TL;DR: CustomTabs close when app is backgrounded, but we need it to stay active. How can we achieve this?
We have an app which uses CustomTabs to login the user. We have added two-factor authentication, but this introduces a problem. When you tap the login button the custom tabs intent is launched as this:
var tcs = new TaskCompletionSource<BrowserResult>();
try
{
var activity = (Activity)Forms.Context;
var builder = new CustomTabsIntent.Builder().EnableUrlBarHiding();
var customTabsIntent = builder.Build();
customTabsIntent.Intent.AddFlags(ActivityFlags.NoHistory);
Action<string> callback = null;
callback = url =>
{
MainActivity.Callbacks -= callback;
tcs.SetResult(new BrowserResult
{
ResultType = BrowserResultType.Success,
Response = url
});
};
MainActivity.Callbacks += callback;
customTabsIntent.LaunchUrl(activity, Android.Net.Uri.Parse(options.StartUrl));
}
catch (Exception ex)
{
throw new Exception($"error: {ex.Message}");
}
return tcs.Task;
This works as expected and you can login, of course now the code for two-factor authentication code is asked, which in most cases means you have to background the app, open your authenticator (authy, Google authenticator etc.) and then come back to the app with the code. The problem is that when we re-open the app the CustomTabs and it's session is completely gone. This means you have to click it again, login again and the same happens all over. I have searched for a solution for days now. Can anyone help us finding a way to keep the CustomTabs and it's session open, so you can just fill in your authenticator code and login happily ever after?
Okay it seems to be that the following code that someone added was the one causing this:
customTabsIntent.Intent.AddFlags(ActivityFlags.NoHistory);
I removed it and it seems to work now. I see some other issues going on, but I don't think this has to do with this issue. If it does I will let you know here.
Related
I'm new with Xamarin form, my question is how can I check user is logged in for entire app, example every time when users go to a new page, it checks for authent. I tried successfully for every page check for auth but is there any another ways to do it? I did some research on internet and some said that i have to auth on the App.cs on OnStart() but the event is not raise when i go to the next page, it starts only when user open the app.
Here is my code, I'm using Google auth.
On the logged in page (HomePage):
public HomePage(NetworkAuthData networkAuthData)
{
if (string.IsNullOrEmpty(networkAuthData.Id))
{
//Always require user authentication
//Application.Current.MainPage.Navigation.PopAsync();
Application.Current.MainPage = new NavigationPage(new SocialLoginPage(oAuth2Service));
}
else
{
BindingContext = networkAuthData;
InitializeComponent();
}
}
It works but when i move these code to app.cs on OnStart() it just run once when opened the app.
I have an app that makes an http request via the localhost to a separate, third-party app which I do not control, and waits for a response from that call before continuing. The workflow goes like this:
User is inside my app
User presses a button, which launches and calls out to the third-party application
User interacts with the third-party application
When the third-party application finishes its work, my app picks up the completed http response, and pulls itself back to the forefront via MoveTaskToFront for the user to continue working.
This functions properly in Android 9 and below, but the last step does not work in Android 10, I believe due to the new restrictions on launching activities from the background.
I have no control over the third-party app, so I cannot modify it to close itself when finished working, or request that the calling app be returned to the foreground when appropriate. Does anyone know of a workaround for this?
Edit: as requested, I've added the code snippet with the call out. This is a Xamarin project, so it's written in C#, but this particular code section is Android-platform-specific, so I am able to make Android system calls.
First I have to bring up the third-party app:
Intent intent = CrossCurrentActivity.Current.AppContext.PackageManager.GetLaunchIntentForPackage("com.bbpos.android.tsys");
if (intent != null)
{
// We found the activity now start the activity
intent.AddFlags(ActivityFlags.ClearTask);
CrossCurrentActivity.Current.AppContext.StartActivity(intent);
}
Then I call into it via the localhost, process the response, and want to switch back to my app.
using (var client = new HttpClient())
{
// by calling .Result we're forcing synchronicity
var response = client.GetAsync("http://127.0.0.1:8080/v2/pos?TransportKey=" + pTransportKey + "&Format=JSON").Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
// as above, forcing synchronicity
string responseString = responseContent.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<GeniusTransactionResponse>(responseString);
var manager = (ActivityManager)Application.Context.GetSystemService(Context.ActivityService);
var test = manager.AppTasks.First().TaskInfo.Id;
manager.AppTasks.First().MoveToFront();
//manager.MoveTaskToFront(CrossCurrentActivity.Current.Activity.TaskId, 0);
return result;
}
else
{
return null;
}
}
Quick update in case anyone else has this same issue: I was able to work around this by adding an Accessibility Service to the project. Simply having an Accessibility Service registered and enabled by the user allows MoveTaskToFront to function as it did in APIs <29; the actual service doesn't need to do anything.
I'm currently developing an Android application in order to display home screen widgets. Those ones are related to Microsoft Outlook (Events + Messages) in order to show incoming events and unread new messages in a kind of dynamic tiles.
The Msal graph library helps me a lot to authenticate and retrieve in formations which contains an identifier for each event / message results
But now I want to know if the outlook application is installed on the user device and if there is a way to open Outlook when the user click on the widget. Moreover if the user can open the corresponding clicked event or message with the identifier.
For example the Event widget currently displaying a birthday event. The user click on it. Then it opens Outlook and display directly that birthday event.
Regards
I don't think this is officially documented somewhere. But here's what you can do to find out about it.
You can list all Microsoft applications installed on your device...
val packages = context.packageManager
.getInstalledApplications(PackageManager.GET_META_DATA)
for (info in packages) {
if(info.packageName.startsWith("com.microsoft", true)){
Log.d("package name:" + info.packageName)
Log.d("Launch Activity: " + context.packageManager.getLaunchIntentForPackage(info.packageName))
}
}
Take a note of the "launch intent" displayed in the LogCat. You can use that to launch Outlook. Just make sure you don't hard-code those values because Microsoft can change those values at any point, for example the activity class can change. So, instead of doing this...
context.startActivity(
Intent().apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
setPackage("com.microsoft.office.outlook")
component = ComponentName("com.microsoft.office.outlook", "com.microsoft.office.outlook.MainActivity")
}
)
Do this...
context.startActivity(
Intent().apply {
action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER)
component = ComponentName(
outlookLaunchIntent?.component?.packageName,
outlookLaunchIntent?.component?.className
)
setPackage(outlookLaunchIntent.package)
}
)
Also, remember that getLaunchIntentForPackage and component can return null, so make sure you check for null values properly
I am relaying a suggestion from a couple of internal folks:
Please try to open the event using one of the following URLs:
ms-outlook://events/open?restid=%s&account=test#om.com (if you have a regular REST id)
ms-outlook://events/open?immutableid=%s&account=test#om.com (if you are using an immutable id)
Since immutable IDs are still in preview stage in Microsoft Graph, and customers should not use preview APIs in their production apps, I think option #1 applies to your case.
Please reply here if the URL works, or not, and if you have other related questions. I requested the couple of folks to keep an eye on this thread as well.
Well, i managed to open the outlook android application with the help of your code #Leo. As im not developping with Kotlin, ill post the JAVA code below :
Intent outlookLaunchIntent = context.getPackageManager().getLaunchIntentForPackage("com.microsoft.office.outlook");
if (outlookLaunchIntent != null) {
context.startActivity(outlookLaunchIntent );
}
Below code to open event/message in a web browser provided by webLink property of the graph API. (I only test for event and the url provided not working. Ill post a new issue on StackOverFlow for that but you already see the issue over there : https://github.com/microsoftgraph/microsoft-graph-docs/issues/4203
try {
Intent webIntent = new Intent(Intent.ACTION_VIEW).setData(Uri.parse(calendarWebLink));
webIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(webIntent);
} catch (RuntimeException e) {
// The url is invalid, maybe missing http://
e.printStackTrace();
}
However im still stuck on the decicive goal of my widget item click which is to open the relative event/email in the Microsoft Outlook Android application.
Microsoft Outlook Android app contains widgets which can achieve what im looking for. So i wonder if it is possible to list its broadcast receivers.
The best thing i found is an old manifest for that app but it doesnt help me.
https://gist.github.com/RyPope/df0e61f477af4b73865cd72bdaa7d8c2
Hi may you try to open the event using one of the url:
ms-outlook://events/open?restid=%s&account=test#om.com (If the
user is having rest id)
ms-outlook://events/open?immutableid=%s&account=test#om.com (If
the user is having immutable id)
After some back and forth I finally got this to work but I had to use version 0.2.0 because I followed the google guide presented in the Readme.
Anyway, Im struggling with handling what will happen when the oAuth token times out. Then it needs to open the browser again to log in or is there a background process available for this as it automatically redirects back to the app because the server remembers the user so there is no need for a new username/password input?
Im getting a refresh token like this :
if(mAuthService == null){
mAuthService = new AuthorizationService(context);
}
mAuthState.performActionWithFreshTokens(mAuthService, new AuthState.AuthStateAction() {
#Override public void execute(
String accessToken,
String idToken,
AuthorizationException ex) {
if (ex != null) {
return;
}
// Getting the access token...
}
});
Thats working fine but after the user is idle for some time it wont work. How to handle this properly?
Solution for my problem was this:
I changed to using offline_access for the token in the scope. Depending on the site/service you're login into if they accept it or not. For me it was accepted and will keep the user logged in for a long time and removes the need to re-login.
When using Firebase invites and accessing the dynamic links at the startup of the app on Android, is there a way to know whether the user just installed the app thanks to the invite or whether it was already installed?
Many thanks,
Borja
EDIT: Thanks to Catalin Morosan for the answer
It turns out that you can find this out using method AppInviteReferral.isOpenedFromPlayStore(result.getInvitationIntent()). In the activity that runs when you click on the invitation:
// Create an auto-managed GoogleApiClient with access to App Invites.
mGoogleApiClientInvite = new GoogleApiClient.Builder(this)
.addApi(AppInvite.API)
.enableAutoManage(this, this)
.build();
// Check for App Invite invitations and launch deep-link activity if possible.
// Requires that an Activity is registered in AndroidManifest.xml to handle
// deep-link URLs.
boolean autoLaunchDeepLink = false;
AppInvite.AppInviteApi.getInvitation(mGoogleApiClientInvite, this, autoLaunchDeepLink)
.setResultCallback(
new ResultCallback<AppInviteInvitationResult>() {
#Override
public void onResult(AppInviteInvitationResult result) {
if (result.getStatus().isSuccess()) {
// Extract information from the intent
Intent intent = result.getInvitationIntent();
String invitationId = AppInviteReferral.getInvitationId(intent);
boolean alreadyUser = AppInviteReferral.isOpenedFromPlayStore(result.getInvitationIntent());
if (alreadyUser) {
// Do stuff...
} else {
// Do other stuff...
}
}
}
});
Based on this Google product form post, the Firebase Dynamic Links library will only check for incoming deep links once per app lifetime, meaning you'd need to uninstall and reinstall the app for it to check again. This feeds into the behavior of the getInvitation() method, and it appears you can imply whether the app was previously installed based on the results of this method.
To me, this seems awfully confusing. At Branch.io we do it completely differently: your link data object will always contain an is_first_session boolean, which you can programmatically handle in any way you choose.