Nav Deep Link from a foreground service - android

I'm trying to navigate to a fragment in my navigation graph from a foreground service notification. I used the below code to create an explicit deep link which didn't work out as expected.
val pendingIntent = NavDeepLinkBuilder(this) // this is the Service context!
.setComponentName(MainActivity::class.java)
.setGraph(R.navigation.primary_app_nav)
.setDestination(R.id.shareSheetFragment)
.setArguments(bundle)
.createPendingIntent()
After reading the guide, I got to know that you need to use the activity context and not any other context (in my case it is service context).
Note that if the provided context is not an Activity, the constructor
uses PackageManager.getLaunchIntentForPackage() as the default
activity to launch, if available.
My question is how do I get hold of the MainActivity context from within my foreground service?

I still haven't figured out a way to do it with explicit deep links, but I settled for a solution which leverages implicit deep link.
First, add a deep link to your fragment in the nav graph XML.
<deepLink android:id="#+id/deepLink"
app:uri="my-app://com.example.app/hotspot?ssid={ssid}&passkey={passkey}" />
Add the <nav-graph /> tag within your <activity /> in the manifest. This will ensure that all the intent filters are created and merged to your manifest.
<nav-graph android:value="#navigation/primary_app_nav" />
Create your PendingIntent for the notification as follows.
fun getNotificationPendingIntent(ssid: String, passkey: String): PendingIntent {
val uri = Uri.parse("my-app://com.example.app/hotspot?ssid=$ssid&passkey=$passkey")
val bundle = Bundle().apply { putParcelable(SHARE_DEEP_LINK_KEY, uri) }
val intent = Intent(this, SailsActivity::class.java).apply {
putExtras(bundle)
// to clear the back stack
addFlags(FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_SINGLE_TOP
or FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK)
}
return PendingIntent.getActivity(this, launchShareSheetCode, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
Finally, you need to handle the deep link intents from your Activity class' onCreate() method.
private fun handleDeepLinkIntent() {
val deepLink = intent.extras?.getParcelable(SHARE_DEEP_LINK_KEY) as? Uri
deepLink?.also {
val intent = Intent(this, SenderService::class.java)
this.bindService(intent, serviceConn, Context.BIND_ABOVE_CLIENT)
navigateDeepLink(it)
}
}
private fun navigateDeepLink(uri: Uri) {
findNavController(R.id.navHostFragment).apply {
if (graph.hasDeepLink(uri))
navigate(uri)
else {
currentDestination?.getAction(id)?.let {
navigate(id)
}
}
}
}
Deep link URI arguments are automatically available to you in the destination fragment.
if (arguments != null) {
val ssid = requireArguments().getString("ssid")
val passkey = requireArguments().getString("passkey")
}

Related

How to handle createChooser's IntentSender without having the component class of the receiver Intent

I am trying to handle the IntentSender of Intent.createChooser() to do something when a user selects an app to share an image on. Most the examples I've found here (posted below), require using a BroadcastReceiver as follows:
Intent receiver = new Intent(context, MyReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, receiver, PendingIntent.FLAG_UPDATE_CURRENT);
String type = "image/*";
Intent share = new Intent(Intent.ACTION_SEND);
share.setType(type);
share.putExtra(Intent.EXTRA_STREAM, awesome_photo_uri);
startActivity(Intent.createChooser(share, "some_title", pendingIntent.getIntentSender()));
My problem with this solution, is located in this line:
Intent receiver = new Intent(context, MyReceiver.class);
The Intent constructor used in these examples require me to make a static MyReceiver class, so I have a class to pass into the second argument of the constructor. But, this causes an issue because I'd like the BroadcastReceiver's onReceive to do stuff in my Fragment. Therefore, I would prefer to create a BroadcastReceiver dynamically in my Fragment.
To no avail, I attempted the following work-around:
Inside MyFragment.kt:
private val receiver: BroadcastReceiver = getBroadcastReceiver()
private val intentFilter = IntentFilter("com.my.app.CHOOSER_ACTION")
override fun onResume() {
requireContext().registerReceiver(receiver, intentFilter)
super.onResume()
}
override fun onPause() {
requireContext().unregisterReceiver(receiver)
super.onPause()
}
private fun shareImage(imageFile: File) {
Intent().apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
action = Intent.ACTION_SEND
type = "image/*"
putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(
requireContext(),
"${myPackageName}.fileprovider",
imageFile
))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
val receiver = Intent("com.my.app.CHOOSER_ACTION")
val pendingIntent = PendingIntent.getBroadcast(requireContext(), 0, receiver, PendingIntent.FLAG_CANCEL_CURRENT)
startActivity(Intent.createChooser(this, "Share image using", pendingIntent.intentSender))
} else {
startActivity(Intent.createChooser(this, "Share image using"))
}
}
}
private fun getBroadcastReceiver() : BroadcastReceiver {
return object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
intent?.extras?.keySet()?.forEach {
Log.v("MyTest", "$it: ${intent.extras?.get(it)}")
}
doSomethingInMyFragment()
}
}
}
Inside AndroidManifest.xml:
<activity android:name="MyActivityThatHasMyFragment" />
<intent-filter>
<action android:name="com.my.app.CHOOSER_ACTION" />
</intent-filter>
</activity>
Unfortunately, the dynamic BroadcastReceiver's onReceive() function is never called after the user presses on a selection. A few questions:
Why does this not work? What am I missing? Am I somehow setting the Intent or IntentFilter incorrectly?
Is it even possible to use a dynamic BroadcastReceiver for handling the IntentSender of createChooser? If not, how can I create a static BroadcastReceiver that triggers something to happen in MyFragment?
Resources:
Get IntentSender object for createChooser method in Android
Get results from Android Chooser
How to tell which app was selected by Intent.createChooser?
What is the purpose of IntentSender?
Intent.createChooser()

Activity Transition API not working

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

Mvvmcross Android Notification Pass Parameter to Existing ViewModel

I have a foreground service with a notification. When the user clicks the notification, it will bring the user back to the main screen viewmodel (or load it again if unloaded).
I want to add 2 actions (pause, stop) to the notification. It is preferred to call the same viewmodel with a parameter to indicate the action type, and let the main screen viewmodel to handle the action. As the main screen viewmodel may have been loaded already, no more initialization will be executed. Passing parameter like showing viewmodel does not work. The existing viewmodel does not know it was triggered from the notification indeed.
How can I pass a different parameter for each action type to the viewmodel, and retrieve it in the viewmodel to act accordingly? Or it should be done in a different way?
This is the code of the notification:
var request = MvxViewModelRequest<RouteLayoutViewModel>.GetDefaultRequest();
var intent = Mvx.Resolve<IMvxAndroidViewModelRequestTranslator>().GetIntentFor(request);
const int pendingIntentId = 0;
PendingIntent pendingIntent = PendingIntent.GetActivity(Application.Context, pendingIntentId, intent, PendingIntentFlags.UpdateCurrent);
var builder = new NotificationCompat.Builder(this);
builder
.SetContentTitle(AppConstants.AppName)
.SetContentText("notification_text")
.SetSmallIcon(Resource.Drawable.Icon)
.SetContentIntent(pendingIntent); ..........
var notification = builder.Build();
StartForeground(AppConstants.SERVICE_RUNNING_NOTIFICATION_ID, notification);
Thanks,
Nick
MvvmCross 5 changed the way the ViewModel parameters are serialized internally. MvvmCross 5 hasn't been updated yet to handle this scenario AFAIK. Here is some sample code demonstrating how to workaround the issue using a BroadcastReceiver currently. This approach will allow you to navigate with parameters after the user clicks on the Android Notification.
When you're building the notification:
var intent = new Intent(context, typeof(YourBroadcastReceiver));
intent.SetAction("YOUR_ACTION");
// Put your other parameters here...
intent.PutExtra("id", id);
var pendingIntent = PendingIntent.GetBroadcast(context, _notificationId, intent, PendingIntentFlags.UpdateCurrent);
...
notificationBuilder.SetContentIntent(pendingIntent);
Then you add a BroadcastReceiver class like so:
[BroadcastReceiver]
public class YourBroadcastReceiver : MvxBroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == "YOUR_ACTION")
{
// TODO: Extract the contents from the intent.
var id = intent.GetIntExtra("id", 0);
// TODO: Navigate using IMvxNavigationService using the parameters pulled from the Intent.
}
}
}

Check which url is opened in custom chrome tabs

Is there any function in chrome custom tabs analogous to onPageStarted of Webview. IN onNavigation.. the bundle is always null
By design this is not possible with Chrome Custom Tabs. You can tell that a user has navigated but you can't tell where they've gone to. See: http://developer.android.com/reference/android/support/customtabs/CustomTabsCallback.html for details of what's possible.
You can see what URL is currently open in Chrome Custom Tabs if you can get the user to trigger a PendingIntent by clicking on a toolbar action button or a menu option.
In your fragment/activity, create a nested BroadcastReceiver class that will handle the incoming intent in it's onReceive() method:
class DigBroadcastReceiver() : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val uri: Uri? = intent.data
if (uri != null) {
Log.d("Broadcast URL",uri.toString())
main.genericToast(uri.toString())
}
}
}
Add the receiver to your manifest file:
<receiver
android:name=".ui.dig.DigTabs$DigBroadcastReceiver"
android:enabled="true" />
Create the PendingIntent and add it to your CustomTabsIntent.Builder:
val sendLinkIntent = Intent(main,DigBroadcastReceiver()::class.java)
sendLinkIntent.putExtra(Intent.EXTRA_SUBJECT,"This is the link you were exploring")
val pendingIntent = PendingIntent.getBroadcast(main,0,sendLinkIntent,PendingIntent.FLAG_UPDATE_CURRENT)
// Set the action button
AppCompatResources.getDrawable(main, R.drawable.close_icon)?.let {
DrawableCompat.setTint(it, Color.WHITE)
builder.setActionButton(it.toBitmap(),"Add this link to your dig",pendingIntent,false)
}
val customTabsIntent: CustomTabsIntent = builder.build()
customTabsIntent.launchUrl(main, Uri.parse(url))
See my article explaining this on Medium.

Android MediaProjectionManager in Service

I want to build an app where I have to use MediaProjectionManager in a Service. But I can not solve it as 'startActivityForResult' can't use in Service class.
I really want to do this from a service, which is how I found this question. This is the closest I've came up, so just throwing this out there, till a better answer comes along. Here's a way to do it from an activity that's almost like doing it from a service:
import static your.package.YourClass.mediaProjectionManager;
public class MainActivity extends Activity {
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(null);
mediaProjectionManager = (MediaProjectionManager)getContext().getSystemService(MEDIA_PROJECTION_SERVICE);
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), 1);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
if (resultCode == Activity.RESULT_OK) {
mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
this.finish();
}
}
}
Then in your service when ever you need permission call
private void openMainActivity() {
Intent mainIntent = new Intent(getContext(), MainActivity.class);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(mainIntent);
}
To make the activity invisible in your AndroidManifest.xml
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#android:style/Theme.NoDisplay"
android:excludeFromRecents="true"
android:screenOrientation="portrait">
</activity>
Caveats:
For a brief second the application you're screenshooting will lose focus.
For a brief second your app will be the foreground app, so don't trip over your own shoelaces
I have used same approach as here https://medium.com/#debuggingisfun/android-10-audio-capture-77dd8e9070f9
in my activity:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, MediaProjectionService.class);
startForegroundService(intent);
mProjectionManager =
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
startActivityForResult(mProjectionManager.createScreenCaptureIntent(),
1);
}
Service itself is just some boilerplate, the notification is required if you want to run in foreground
class MediaProjectionService : Service() {
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val builder = NotificationCompat.Builder(this, "messages")
.setContentText("streaming enabled")
.setContentTitle("Stream to e-ink")
startForeground(101, builder.build())
}
}
override fun onBind(intent: Intent): IBinder? {
return null
}
}
I am not an android expert and I am using Flutter - apperantly you also need to create a notification channel first - here is my code for Flutter Application - it's probably similar with non-flutter
class StreaminkApplication : FlutterApplication() {
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("messages", "Messages", NotificationManager.IMPORTANCE_LOW)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
}
If you use application, you should change it in manifest - in my case:
<application
android:name=".StreaminkApplication"
Ok, I have managed to do this its not the cleanest of ways but works:
Local Bound Service: Make your Service a Local Bound Service, which means your service will pass a reference of itself to the the Main Activity.
Pass an Instance of the Activity to the Service : Pass the reference of your activity to the service in the Service Connection-> onServiceConnected() in the Activity class
Call the startActivityForResult: Use the Activity Instance in your service to Call the startActivityForResult function.
Note:
Upon being called the startActivityForResult will be caught in the onActivityResult() in the Activity Class. You will need to override this function in the Activity and use the service reference to return back to the service class. Also you can unbind the Activity, from the service upon completion.
I hope this makes sense, sorry for not including any code. If you have any questions ill be glad to help you in further detail.

Categories

Resources