I wish to use the new Activity transition API and after following the tutorial here I am not able to get the desired result.
This is the code I have for setting the activity transition I wish to detect :
public void setActivityTransitions() {
transitionList = new ArrayList<>();
ArrayList<Integer> activities = new ArrayList<>(Arrays.asList(
DetectedActivity.STILL,
DetectedActivity.WALKING,
DetectedActivity.ON_FOOT,
DetectedActivity.RUNNING,
DetectedActivity.ON_BICYCLE,
DetectedActivity.IN_VEHICLE));
for (int activity :
activities) {
transitionList.add(new ActivityTransition.Builder()
.setActivityType(activity)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER).build());
transitionList.add(new ActivityTransition.Builder()
.setActivityType(activity)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_EXIT).build());
}
}
And then requesting the activity transition updates :
ActivityTransitionRequest activityTransitionRequest = new ActivityTransitionRequest(transitionList);
Intent intent = new Intent(context, ActivityDetectorTransitionService.class);
intent.setAction("com.test.activityrecognition.START_ACTIVITY_TRANSITION_DETECTION_ALARM");
PendingIntent pendingIntent = PendingIntent.getService(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Task<Void> task = ActivityRecognition.getClient(context).requestActivityTransitionUpdates(activityTransitionRequest, pendingIntent);
task.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void result) {
System.out.println("onSuccess");
}
});
task.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
e.printStackTrace();
System.out.println("onFailure");
}
});
And this is the broadcastreceiver :
public class ActivityDetectorTransitionService extends BroadcastReceiver {
private static final String TAG = "ActivityDetectorTransitionService";
#Override
public void onReceive(Context context, Intent intent) {
if (ActivityTransitionResult.hasResult(intent)) {
ActivityTransitionResult activityTransitionResult = ActivityTransitionResult.extractResult(intent);
ActivityDetectorTransitionAPI.getInstance().handleActivityRecognitionResult(activityTransitionResult);
}
}
}
(The name has service in it cause initially I had kept it service but still not working.)
and in manifest :
<receiver
android:name=".tracking.activityrecognition.ActivityDetectorTransitionService">
<intent-filter>
<action android:name="com.test.activityrecognition.START_ACTIVITY_TRANSITION_DETECTION_ALARM"/>
</intent-filter>
</receiver>
You are using PendingIntent.getService() in combination with a BroadcastReceiver.
To receive pending intents with a BroadcastReceiver you have to retrieve the PendingIntent instance using PendingIntent.getBroadcast(). The corresponding developer guide concerning intents and intent filters can be found here.
Since Android 8 there are several background service limitations. Using an IntentService only works when the app is in foreground. To receive activity transition updates after the app was closed you even have to use a BroadcastReceiver. For this purpose the BroadcastReceiver has to be registered in the application manifest with the corresponding permission, as Jan Maděra already mentioned.
<receiver android:name="com.mypackage.ActivityTransitionBroadcastReceiver"
android:exported="false"
android:permission="com.google.android.gms.permission.ACTIVITY_RECOGNITION">
<intent-filter>
<action android:name="com.mypackage.ACTION_PROCESS_ACTIVITY_TRANSITIONS" />
</intent-filter>
</receiver>
Furthermore onReceive() should only respond to your specific action, since intent filters are not guaranteed to be exclusive.
public class ActivityTransitionBroadcastReceiver extends BroadcastReceiver {
public static final String INTENT_ACTION = "com.mypackage" +
".ACTION_PROCESS_ACTIVITY_TRANSITIONS";
#Override
public void onReceive(Context context, Intent intent) {
if (intent != null && INTENT_ACTION.equals(intent.getAction())) {
if (ActivityTransitionResult.hasResult(intent)) {
ActivityTransitionResult intentResult = ActivityTransitionResult
.extractResult(intent);
// handle activity transition result ...
}
}
}
}
Requesting activity transition updates using PendingIntent.getBroadcast():
ActivityTransitionRequest request = new ActivityTransitionRequest(transitionList);
Intent intent = new Intent(context, ActivityTransitionBroadcastReceiver.class);
intent.setAction(ActivityTransitionBroadcastReceiver.INTENT_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
Task<Void> task = ActivityRecognition.getClient(context)
.requestActivityTransitionUpdates(request, pendingIntent);
Be aware that activity transition updates can be received delayed. This depends on the device and can also be affected by power management restrictions.
This is an old post, but this answer might help someone.
Keep in mind that latency might actually be the problem, as it was in my case. I thought my implementation wasn't working, but in reality it was. The Activity Transitions API just has a huge delay of about 1 minute to notify you of transitions. So try walking around or driving for a few minutes to start receiving notifications.
I faced similar issue but helped me add receiver to the manifest
<receiver
android:name=".service.ActivityTransitionReceiver"
android:permission="com.google.android.gms.permission.ACTIVITY_RECOGNITION"
android:exported="false" />
I also tried the aforementioned Codelab tutorial, as well as a few other examples, but none of them worked; BroadcastReceiver.onReceive() was never called, no matter how I set it up.
What did work was to use requestActivityUpdates() instead of requestActivityTransitionUpdates(). According to the document, requestActivityTransitionUpdates() is a better choice, because it improves accuracy and consumes less power, but it's not better choice for me if it doesn't do what it's supposed to do. Here is the summary on what I did.
[AndroidManifest.xml]
<receiver
android:name=".TransitionUpdatesBroadcastReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="TRANSITION_UPDATES" />
</intent-filter>
</receiver>
// This is in your Activity/Fragment.
private val pendingIntent: PendingIntent by lazy {
val intent = Intent(context, TransitionUpdatesBroadcastReceiver::class.java)
intent.action = TRANSITION_UPDATES
PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityRecognition.getClient(context)
.requestActivityUpdates(1_000, pendingIntent) <-- Here.
}
override fun onDestroy() {
super.onDestroy()
ActivityRecognition.getClient(context)
.removeActivityUpdates(pendingIntent)
}
class TransitionUpdatesBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// Do what you need to do with intent.
}
}
At some point it seems that the following intent for an explicit class stopped working (or maybe never worked?):
Intent intent = new Intent(context, ActivityDetectorTransitionService.class);
Instead, I created the intent by passing in the action in the intent constructor, as follows:
Intent intent = new Intent("com.test.activityrecognition.START_ACTIVITY_TRANSITION_DETECTION_ALARM");
...and then I started to get callbacks successfully from the Activity Transition API.
Note that this approach is used in the latest codelab as well:
https://github.com/googlecodelabs/activity_transitionapi-codelab/blob/master/complete/src/main/java/com/google/example/android/basicactivityrecognitiontransitionsample/MainActivity.java#L134
Related
Having quite a bit of trouble getting my foreground service to simply dismiss on swipe or by the clear all notifications button. I know there are several questions like this, but I have read them and will list some of what I've already tried. I tried this method, where you pass a simple intent with an extra into a pending intent, then add that pending intent to setDeleteIntent(). That didn't work, it never gets called.
I also tried this (second highest answer, "fully fleshed out" version), with the same results. Which ties in to the "answer" of this question as well. With how many apps have notifications that are dismissible with a swipe or clear all button, I must be missing something very obvious and I can't figure out what.
What my current code looks like (all of this inside of my service class).
BroadcastReceiver class
public class NotificationDismissedReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// this is never called
stopSelf();
}
}
// in the manifest for that BroadcastReceiver, have also tried it with these values false
android:exported="true"
android:enabled="true"
Pending intent
private PendingIntent createOnDismissedIntent(Context context, int notificationId) {
Intent intent = new Intent(context.getApplicationContext(), NotificationDismissedReceiver.class);
PendingIntent pendingIntent =
PendingIntent.getBroadcast(context.getApplicationContext(),
notificationId, intent, 0);
return pendingIntent;
}
In the notification builder (101 is what I startForeground with)
.setDeleteIntent(createOnDismissedIntent(this, 101))
Edit:
Separate file receiver used instead of the inner class version.
public class NotificationDismissedReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
int notificationId = intent.getExtras().getInt("notificationId");
/* Your code to handle the event here */
if(notificationId == 101){
Intent stopIntent = new Intent(context, AssistorServiceClass.class);
context.stopService(stopIntent);
}
}
}
Edit2
To be clear, I'm not trying to stop the foreground notification and keep the service alive. I want them both just gone, as if I called stopSelf within the service class.
I have a simple Android Kotlin app, and part of what it does is listen for when power is connected and disconnected and perform an action
This is my old code, and it worked totally fine while targeting devices below Oreo.
AndroidManifest.xml
<receiver android:name=".ChargingUtil$PlugInReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
</intent-filter>
</receiver>
ChargingUtil.kt
class ChargingUtil (context: Context){
/*... Some other charging-related functions here ... */
class PlugInReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("thisistest", "Power was changed")
// Here I do some logic with `intent.action`
}
}
}
There have been some changes to how to implement Broadcasts, in later Android versions: https://developer.android.com/guide/components/broadcasts
What I've tried so far:
I tried following this documentation, but their implementation is actually the same as my current code (which only works below Android 8).
I also found this question, but the only solution, was to periodically check if power is connected or not. I don't think that is so viable for me, since my app needs to know instantly when the charging state is changed.
So my question is:
How to call a function when power is connected/ disconnected? While taking account of the additional restrictions that systems running Android 8 or later impose on manifest-declared receivers.
Note: I am using Kotlin, and would like to avoid the use of deprecated packages
I am a bit of a noob when it comes to Android, so sorry if there is actually an obvious solution what I just missed. Thanks in advance for any help.
The question you have referred already has the answer.
Use ACTION_BOOT_COMPLETED to start a foreground service after boot. This service should register ACTION_POWER_CONNECTED broadcast. You can also start this service in a separate process. As soon as the power is connected/disconnected, you will receive the broadcast in service class where you can run your required method.
public class MyService extends Service {
private String TAG = this.getClass().getSimpleName();
#Override
public void onCreate() {
Log.d(TAG, "Inside onCreate() API");
if (Build.VERSION.SDK_INT >= 26) {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
mBuilder.setSmallIcon(R.drawable.ic_launcher);
mBuilder.setContentTitle("Notification Alert, Click Me!");
mBuilder.setContentText("Hi, This is Android Notification Detail!");
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// notificationID allows you to update the notification later on.
mNotificationManager.notify(100, mBuilder.build());
startForeground(100, mBuilder.mNotification);
IntentFilter filter1=new IntentFilter();
filter1.addAction(Intent.ACTION_POWER_CONNECTED);
registerReceiver(myBroadcastReceiver,filter1);
}
}
#Override
public int onStartCommand(Intent resultIntent, int resultCode, int startId) {
Log.d(TAG, "inside onStartCommand() API");
return startId;
}
#Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "inside onDestroy() API");
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
BroadcastReceiver myBroadcastReceiver =new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// call your method
}
};
}
You can also register a JobScheduler with setRequiresCharging true. this will start the call JobScheduler's job when the power state is changed.
ComponentName serviceComponent = new ComponentName(context, TestJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
builder.setMinimumLatency(1 * 1000); // wait at least
builder.setOverrideDeadline(3 * 1000); // maximum delay
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
builder.setRequiresDeviceIdle(true);
builder.setRequiresCharging(true);
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
jobScheduler.schedule(builder.build());
Starting from Android 8.0 (API level 26 and higher), you can't use static receivers to receive most Android system broadcasts read here ,
A BroadcastReceiver is either a static receiver or a dynamic receiver, depending on how you register it:
To register a receiver statically, use the <receiver> element in your
AndroidManifest.xml file. Static receivers are also called manifest-declared receivers.
To register a receiver dynamically, use the app context or activity context. The
receiver receives broadcasts as long as the registering context is valid, meaning as
long as the corresponding app or activity is running. Dynamic receivers are also called
context-registered receivers.
So you need to register your Receiver dynamically , go to AndroidManifest.xml and delete the <receiver> tag , and register it at your activity .
private MyBatteryReceiver mReceiver = new MyBatterReceiver();
Then use IntentFilter for the power actions :
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
filter.addAction(Intent.ACTION_POWER_CONNECTED)
All you have left is to register and dismiss your register
this.registerReceiver(mReceiver, filter); // using activity context.
this.unregisterReceiver(mReceiver); // implement on onDestroy().
Thanks to #Derek's answer, plus a couple of changes I got this to work. Posting the working solution to here, in case it's any help to anyone else.
PowerConnectionReciever.kt
import android.content.Intent
import android.content.BroadcastReceiver
import android.os.IBinder
import android.content.IntentFilter
import android.app.Service
import android.content.Context
class PowerConnectionService : Service() {
private var connectionChangedReceiver: BroadcastReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// This block gets run whenever the power connection state is changed
when {
intent.action == Intent.ACTION_POWER_CONNECTED ->
powerWasConnected()
intent.action == Intent.ACTION_POWER_DISCONNECTED ->
powerWasDisconnected()
}
}
}
override fun onCreate() {
val connectionChangedIntent = IntentFilter()
connectionChangedIntent.addAction(Intent.ACTION_POWER_CONNECTED)
connectionChangedIntent.addAction(Intent.ACTION_POWER_DISCONNECTED)
registerReceiver(connectionChangedReceiver, connectionChangedIntent)
}
override fun onStartCommand(
resultIntent: Intent, resultCode: Int, startId: Int): Int {
return startId
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(connectionChangedReceiver)
}
override fun onBind(intent: Intent): IBinder? {
return null
}
private fun powerWasConnected() {
// Do whatever you need to do when the power is connected here
}
private fun powerWasDisconnected() {
// And here, do whatever you like when the power is disconnected
}
}
Then within my MainActivity.kt file, added this function, which is called within the onCreate hook.
private fun startPowerConnectionListener() {
val serviceComponent = ComponentName(this, PowerConnectionService::class.java)
val builder = JobInfo.Builder(0, serviceComponent)
builder.setMinimumLatency((200)) // wait time
builder.setOverrideDeadline((200)) // maximum delay
val jobScheduler = this.getSystemService(JobScheduler::class.java)
jobScheduler.schedule(builder.build())
}
The only other changes that I needed, were in the AndroidMainifest.xml
A permission for the FOREGROUND_SERVICE is added as a first-level item:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
And of course it's also required to register the new PowerConnectionService, this goes within the relevant activity node
<service
android:name=".PowerConnectionService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true">
</service>
So, I've been trying to achieve that for at least five hours today and I've tried literally ANY solution found anywhere.
I'm programming a little clock app, using AlarmManager to make the app ring. It works fine when my app is open or minimized. I'm trying to make it work when the app is closed, and that's the problem. So, there is the piece of code that sets the AlarmManager :
AlarmManager am = (AlarmManager) ctxt.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(ctxt, AlarmService.class);
PendingIntent pen = PendingIntent.getBroadcast(ctxt, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
am.setExact(AlarmManager.RTC_WAKEUP, nextRing.getTime(), pen);
(Here, ctxt is the context, I've tried both getApplicationContext() and getBaseContext() and nextRing.getTime() is a long that represents the date)
Then, I have my AlarmService class (wich used to be a service, which explain the name, but is now a BroadcastReceiver and I just don't want to rename it now)
public class AlarmService extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
/*Intent cust = new Intent(context, CustomIntent.class);
context.startService(cust);*/
Bundle extras = intent.getExtras();
Intent newIntent = new Intent(context, AlarmActivity.class);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
newIntent.putExtra("AlarmName", extras.getString("AlarmName"));
context.startActivity(newIntent);
}
}
So this is the try with only the BroadcastReceiver, wich doesn't work obviously, so I tried to add a IntentService (commented out code at the top) which has the following code
public class CustomIntent extends IntentService {
public CustomIntent() {
super("CustomIntent");
}
#Override
protected void onHandleIntent(Intent intent) {
Intent newIntent = new Intent(getBaseContext(), AlarmActivity.class);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplication().startActivity(newIntent);
}
}
... and that's not working either ! Finally, here's the manifest I use :
<application>
[Here goes stuff that have nothing to do with my bug]
<receiver android:name=".custom_classes.AlarmService"/>
<service
android:name="com.group9.abclock.custom_classes.CustomIntent"
android:enabled="true" >
<intent-filter>
<action android:name="com.group9.abclock.custom_classes.CustomIntent" />
</intent-filter>
</service>
</application>
Sorry for the (very) long post but I tough I should explain anything I tried. Thanks in advance if you can help !
If your application is closed, you can't trigger a BroadcastReceiver because it's not registered, and you can't use Context methods because there is no Context.
If you want to execute tasks while the application is closed, you have to create another project with a Service, and start it with your application.
A Service runs in background until someone kill it, and once it's started, the main application is not needed anymore. So the ring functionality have to be implemented in this service.
Just remember that the AlarmManager.setExact, is not that exact from API 23, due to Doze Mode.
I want some methods to execute when I click on Notification Action Button.
I have searched on this site, but everything seems to be in order and my IntentService is not being called.
My Action-Button Intent
Intent off = new Intent();
off.setAction("action");
off.putExtra("test", "off");
PendingIntent pOff = PendingIntent.getService(context, 22, off, 0);
Notification Builder
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(/**/)
.setContentTitle(/**/)
.setContentText(/**/)
.addAction(/**/, "Off", pOff)
.setContentIntent(pendingIntent)
.setDefaults(Notification.DEFAULT_SOUND)
.setAutoCancel(true);
Intent Service Class
public class NotificationServiceClass extends IntentService {
public NotificationServiceClass(String name) {
super(name);
}
public NotificationServiceClass () {
super("NotificationServiceClass");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.i("test", "onHandle");
if (intent.getAction().equals("action")) {
Log.i("test", "action");
Bundle bundle = intent.getExtras();
if (bundle != null) {
Log.i("test", "onHandleBundleNotNull");
if (bundle.containsKey("test")) {
Log.i("test", bundle.getString("test"));
}
}
}
}
}
XML Declaration for Service class
<service
android:name=".Manager.NotificationServiceClass"
android:exported="false">
</service>
Per the Intents and Intent Filters training, the Intent you've built is an implicit Intent:
Implicit intents do not name a specific component, but instead declare a general action to perform, which allows a component from another app to handle it. For example, if you want to show the user a location on a map, you can use an implicit intent to request that another capable app show a specified location on a map.
What you actually want is an explicit intent: one that specifies the component to start by name as per the note on the same page:
Note: When starting a Service, you should always specify the component name. Otherwise, you cannot be certain what service will respond to the intent, and the user cannot see which service starts.
When constructing your Intent, you should use
// Note how you explicitly name the class to use
Intent off = new Intent(context, NotificationServiceClass.class);
off.setAction("action");
off.putExtra("test", "off");
PendingIntent pOff = PendingIntent.getService(context, 22, off, 0);
In looking at your code, I do not see you telling the PendingIntent what class to use for your service.
You should add:
off.setClass(this, NotificationServiceClass.class);
Otherwise the PendingIntent has nothing to do.
I've found a lot of solutions here about the Alarm Manager but none of them seemed to work.
I create the background service with:
public void scheduleSync() {
Intent intent = new Intent(this, SyncReceiver.class);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), SYNC_INTERVAL, pendingIntent);
Log.d(TAG, "Sync scheduled.");
}
The SyncReceiver class is:
public class SyncReceiver extends BroadcastReceiver {
private static final String TAG = "SyncReceiver";
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, WebBackendSyncService_.class);
context.startService(i);
Log.d(TAG, "WebBackendSyncService started.");
}
}
And that is the WebBackendSyncService defined with Android Annotations:
#EIntentService
public class WebBackendSyncService extends IntentService {
public static final String ACTION = "com.invoicing.networking.WebBackendSyncService";
private static final String TAG = "WebBackendSyncService";
#RestService
APIService restClient;
public WebBackendSyncService() { super(ACTION);}
#Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "Handling sync intent.");
sendInvoices();
}
#Background
void sendInvoices() {
SyncData.sendInvoices(restClient);
}
}
Service and Broadcast receiver in the manifest:
<service
android:name=".networking.WebBackendSyncService_"
android:exported="false" />
<receiver
android:name=".networking.SyncReceiver"
android:process=":remote" />
Looking at those line for the past couple of hours pushed me to ask for help here. I hope you'll see something that I'm missing.
Looking at the console output it gets to "Sync scheduled."
First, your <service> element has a rogue underscore in the android:name attribute that you need to remove.
Second, get rid of android:process=":remote". Forking a whole process for a two-line BroadcastReceiver is not very efficient, and it will interfere with the next fix.
Third, switch from BroadcastReceiver to WakefulBroadcastReceiver and follow the instructions for using that class, as right now the device will readily fall asleep either before or during the service's work.
Resolved after removing unused dependencies and multi-dex config. Zero changes to the code itself, however, it works for some unknown reason.