OAuth2 CallBack Google Custom Tabs - android

I am developing an Android Application that uses the FitBit API to retrieve a User's data. These are then used further in the application. In order to make this work, OAuth2 is used for authorization. First step is to get the user's consent to use data in a particular scope.
In order to make this work, I use Google Custom Tabs as prescribed by FitBit. The url works in a web browser, when I test it on an Android device it works too, directs me to the User Consent Page. When I hit the "Agree" button, I do not get redirected to the Application. Instead, I end up on the callback_url page with the Authorization Code.. Still I get nothing back through "System.in.println()". HOW DO I END UP BACK IN MY APPLICATION WITH THE AUTHORIZATION CODE IN SYSTEM.IN?
The following is the page I end up on..
I use a redirect_url for Development purposes, "http://locallhost.com/", to allow me to test in development. This is set up in the Manifest as an Intent Filter (see below), the Application settings of my Application at Fitbit API EndPoint.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final CustomTabsIntent intent = new CustomTabsIntent.Builder().build();
final String url = "https://www.fitbit.com/oauth2/authorize/myApplicationParameters";
intent.launchUrl(this, Uri.parse(url));
System.out.println(in.hasNextLine());
The Manifest
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:host="locallhost.com/"
android:scheme="http"/>
</intent-filter>

I ended up using callback_uri "niels://fitbitcallback". It was confusing to the Android Studio to use http:// or https://. This did not work for me. On top of that I used an additional activity to redirect the user to. Also adapted the Android Manifest accordingly.

Related

How to send data from my TWA webapp to my app?

With Crosswalk I had a very convenient javascript-to-app interface so I could call a java function from javascript and share data from my webapp to my android app.
How can I achieve this with Custom Tabs (or Trusted Web Activity) ?
There seems to be no way at all. There should be, especially when my app and my game/webapp are from the same author.
For example, I do not trust LocalStorage, especially now with Custom Tabs, it may get cleaned, or the user may uninstall the browser and install another one, so the saved data will be lost and the user will be angry at the app for the loss of the saved data, not even understanding that the data were in the browser, not in the app. So I used to have my webapps call the app to save datas.
Another example, when the Custom Tab uses Firefox instead of Chrome, then speech synthesis won't be available. I can detect it easily in my webapp. But I want my webapp to call the app and send it the words to pronounce. That is what I was doing with Crosswalk since it didn't support speech neither.
I understand that webviews are more appropriate for my use than Custom Tabs, but when the webview can't be used on a device (especially Android <5) then my app doesn't have a lot of other options than opening a Custom Tab instead (or Trusted Web Activity if available). I can't use Crosswalk anymore, it is discontinued and still full of serious bugs. And other solutions such as GeckoView or Alibaba Gcanvas are not ready.
edit:
In this article about Trusted Web Activity https://developers.google.com/web/updates/2017/10/using-twa I read
Nevertheless, you can coordinate with the web content by passing data
to and from the page in URLs (e.g. through query parameters, custom
HTTP headers, and intent URIs.)
edit:
I've been reading many pages, Intents and deep-linking are still obscure to me though, but here is what I tried.
I added an intent filter for a custom action :
<receiver android:name=".OutgoingReceiver" android:enabled="true">
<intent-filter>
<action android:name="custom_tabs_js_interface" />
</intent-filter>
</receiver>
I created a class for that receiver :
public class OutgoingReceiver extends BroadcastReceiver {
public static final String CUSTOM_INTENT = "custom_tabs_js_interface";
#Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received" , Toast.LENGTH_SHORT).show();
}
and I call it in javascript with
location.href="intent:#Intent;action=custom_tabs_js_interface;end";
I don't even pass data for now, I just try to call it.
but nothing happens...
Yes broadcast receiver doesn't work for some reason, probably security. But you can use an Activity in place of Broadcast Receiver to do this as below.
Use custom Android Intent Uri with custom host, query parameters, package, scheme and intent action. Invoke this intent uri from your javascript/html code. Example
"intent://myhost?key=value#Intent;scheme=myscheme;package=my.app.package;action=someaction;end"
Also declare an activity in the manifest file with intent filter to handle the specific host, scheme & action. Example
<activity android:name="my.app.package.ReceiverActivity">
<intent-filter>
<action android:name="someaction" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="myhost"
android:scheme="myscheme" />
</intent-filter>
</activity>
Now in the activity, handle the intent and extract the data from query parameters. Do whatever you want with the data and probably finish the activity in case you want to go back to the same screen. Example
public class ReceiverActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String value = getIntent().getData().getQueryParameter("key");
if (value != null) {
// value is basically your data
}
// in case you want to go back to same screen
finish();
}
}
And that is it. You have data for your disposal at an Android Activity. This ReceiverActivity could (preferrably) belong to same TWA app. Now from this Receiver Activity, you can easily send/share the data to any other apps. Hope this helps.
You can use a hybrid solution:
Use custom tabs as you want for game.
Your server can call your app when is needed using socket programming or push notifications to get the data you need to save in your app.
Also if sending data to your app is unsuccessful your game can warns user in browser at game.
Support for older android versions is encouraged as far as reasonably possible. Platform versions distribution (https://developer.android.com/about/dashboards/) at the moment this post was written states that you would lose around 12.7% of devices if you drop support for version 4.
Consider raising your minimum version to Lollipop where WebView was moved to an APK and use it. You will gain time and implementation simplicity since you'll be able to call #JavascriptInterface annotated methods while keeping your users engaged inside your app (https://developer.android.com/guide/webapps/webview_.

VK Login error while logging in through application and not webView Dialog

Good day.I have an issue with a alraedy released social network application where users will not be able to login through VK application.The issue is that i get the json error where VK pretends the error to be the sdk fingerprint error.I have searched a lot,and anything i found was related to the actual hash key/ssha fingerpring key and package name.Hurry to tell you everything is set up fully correct as if i have no VK application isntalled on the phone the login going through perfectly without any issue,but if i install the VK application and try to login through my application within the VK application it throws the json error of fingerprint,so i have no clue what VK developers did and try to do,but if someone can help me it will be great!
Here is how i initialize the VK inside the application tag.
VKAccessTokenTracker vkAccessTokenTracker = new VKAccessTokenTracker() {
#Override
public void onVKAccessTokenChanged(#Nullable VKAccessToken oldToken, #Nullable VKAccessToken newToken) {
sharedHelper.putVkAccessToken(newToken.accessToken);
}
};
vkAccessTokenTracker.startTracking();
VKSdk.customInitialize(this, appId, sharedHelper.getVkAccessToken());
And whenever i need to login user through the VK i call the simple method like this
VKSdk.login(this, vkScopes);
but the json issue inside the application of VK just killing me.Please help me to solve the issue if it is the issue on my sied.
I have been faced with this weird problem for some days, until I found the probable solution.
Open your android.manifest file and put the following intent filter into activity (activities) in which VK authorization call is used, or activity (activities) that contain fragment(s) in which VK authorization call is used:
<intent-filter>
<data android:scheme="vk1234567" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Replace vk1234567 with your VK application's id, and two letters "vk" should be at the beginning of the id as well.
Also FYI, SHA-1 fingerprint that signs your APK should be specified in VK application console without : commas and spaces.

Android Chrome custom tabs / Fitbit web API won't redirect if app is already authorized. (OAuth2.0)

I intend on creating a third party fitbit app for alarm synchronizing.
However, I have encountered some difficulties regarding the registration of my app, more explicitly on getting the access token even thou my client is already registered to the app. ( Considering the scenario that a user reinstall his application).
I'm using Chrome custom tabs (as WebView is prohibited by FitBit) to request the access token :
String url = "https://www.fitbit.com/oauth2/authorize?" +
"response_type=token" +
"&client_id=XXXXXX" +
"&scope=activity"+
"&redirect_uri=fitbittester://logincallback";
customTabsIntent.launchUrl(MainActivity.this, Uri.parse(url));
Upon redirecting to the custom scheme defined with an intent-filter:
<activity
android:name=".TestActivity"
android:label="TestActivity"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="fitbittester" android:host="logincallback" />
</intent-filter>
</activity>
TestActivity should launch, where I'll get my AccessToken from the given Intent:
public class TestActivity extends AppCompatActivity {
String string;
#Override
protected void onNewIntent(Intent intent) {
string = intent.getDataString();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
onNewIntent(getIntent());
Toast.makeText(TestActivity.this, string , Toast.LENGTH_LONG).show();
Log.e("TAG", string);
Log.e("TAG", string.substring(string.indexOf("&access_token")+14));
}
}
Everything works fine on the first run (providing the fact that the client is not already authorized), but after that if want to get my access token again ( I know I should store it locally - SharedPreferences most likely, but this is for testing purposes only) chrome custom tabs will open and stay on an empty page (apparently it won't properly redirect).
I have read the FitBit WEB API and it says the following:
If an application using the Implicit Grant flow sends a user to the authorization page before the previously issued access token has expired, the user will not be prompted unless the scope has increased. The user will be redirected immediately to the application with an access token.
So my question is if there is a fault in my thinking about the problem or
a chrome custom tabs fault I should intercept?
Thank you very much in advance.
I have found a workaround for this problem.
Basically I'm inserting a new parameter in the Url with the query for the Fitbit API. ( "&prompt=login" ) . This parameter will prompt the user to re-login every time it queries for the authorization token, logging him out if it is already logged in.
So I guess fitbit does a 302 redirect when user is already logged in. So I used this solution (mixed this solution with CustomTabActivityHelper from Chrome tab demo ) and it fixed the issue. Yay.
I was able to "fix" the issue by calling the warmup function before
loading the url that redirects.
Chrome Custom Tabs redirect to Android app will close the app

android development: how does my intent / icon get added to user's list like other social media in Android

I would like to allow users to highlight text and post it to my web site.
Is there a way to create an Android Intent running as a service which will show up on the social media / share list that appears in the Android system?
You know, when the user selects a picture and then the three dotted share icon appears and gives a list? How can I get my application's intent and icon added to the list just as the Twitter, FB etc. ones that show up?
To receive data from an external application in your application vis Share functionality, you need to create an activity in your application which will accept the incoming data. Add the following details in AndroidManifest.xml for the Activity which will accept the data:
<activity android:name=".ui.MyActivity" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
So , now if any application, shares a single image, your application will be visible in the list, and if the user selects your application, ui.MyActivity activity will be launched. You can change or add multiple mimeType as per your requirement.
Also, you can add/change action as MULTIPLE_SEND to receive multiple shared files.
Once this is done, in onCreate() method of your activity, you can fetch the data in the following manner:
void onCreate (Bundle savedInstanceState) {
...
// Get intent, action and MIME type
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
//Play with the data, as per your need
...
}
You can get more details regarding this, in the Official Documentation

Android / iOS - Custom URI / Protocol Handling

Is there a way to define some kind of handling mechanism in Android and iOS that would allow me to do intercept either of the following:
myapp:///events/3/
- or -
http://myapp.com/events/3/
I'd like to 'listen' for either the protocol or the host, and open a corresponding Activity / ViewController.
I'd like too if these could be as system wide as possible. I imagine this will be more of an issue on iOS, but I'd ideally be able to click either of those two schemes, as hyperlinks, from any app. Gmail, Safari, etc.
EDIT 5/2014, as this seems to be a popular question I've added much detail to the answer:
Android:
For Android, refer to Intent Filter to Launch My Activity when custom URI is clicked.
You use an intent-filter:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
this is attached to the Activity that you want launched. For example:
<activity android:name="com.MyCompany.MyApp.MainActivity" android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" android:host="com.MyCompany.MyApp" />
</intent-filter>
</activity>
Then, in your activity, if not running, the activity will be launched with the URI passed in the Intent.
Intent intent = getIntent();
Uri openUri = intent.getData();
If already running, onNewIntent() will be called in your activity, again with the URI in the intent.
Lastly, if you instead want to handle the custom protocol in UIWebView's hosted within your native app, you can use:
myWebView.setWebViewClient(new WebViewClient()
{
public Boolean shouldOverrideUrlLoading(WebView view, String url)
{
// inspect the url for your protocol
}
});
iOS:
For iOS, refer to Lauching App with URL (via UIApplicationDelegate's handleOpenURL) working under iOS 4, but not under iOS 3.2.
Define your URL scheme via Info.plist keys similar to:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourcompany.myapp</string>
</dict>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Then define a handler function to get called in your app delegate
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
// parse and validate the URL
}
If you want to handle the custom protocol in UIWebViews hosted within your native app, you can use the UIWebViewDelegate method:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *urlPath = [request URL];
if (navigationType == UIWebViewNavigationTypeLinkClicked)
{
// inspect the [URL scheme], validate
if ([[urlPath scheme] hasPrefix:#"myapp"])
{
...
}
}
}
}
For WKWebView (iOS8+), you can instead use a WKNavigationDelegate and this method:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
NSURL *urlPath = navigationAction.request.URL;
if (navigationAction.navigationType == WKNavigationTypeLinkActivated)
{
// inspect the [URL scheme], validate
if ([[urlPath scheme] hasPrefix:#"myapp"])
{
// ... handle the request
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
}
//Pass back to the decision handler
decisionHandler(WKNavigationActionPolicyAllow);
}
Update: This is a very old question, and things have changed a lot on both iOS and Android. I'll leave the original answer below, but anyone working on a new project or updating an old one should instead consider using deep links, which are supported on both platforms.
On iOS, deep links are called universal links. You'll need to create a JSON file on your web site that associates your app with URLs that point to parts of your web site. Next, update your app to accept a NSUserActivity object and set up the app to display the content that corresponds to the given URL. You also need to add an entitlement to the app listing the URLs that the app can handle. In use, the operating system takes care of downloading the association file from your site and starting your app when someone tries to open one of the URLs your app handles.
Setting up app links on Android works similarly. First, you'll set up an association between your web site(s) and your app, and then you'll add intent filters that let your app intercept attempts to open the URLs that your app can handle.
Although the details are obviously different, the approach is pretty much the same on both platforms. It gives you the ability to insert your app into the display of your web site's content no matter what app tries to access that content.
Original answer:
For iOS, yes, you can do two things:
Have your app advertise that it can handle URL's with a given scheme.
Install a protocol handler to handle whatever scheme you like.
The first option is pretty straightforward, and described in Implementing Custom URL Schemes. To let the system know that your app can handle a given scheme:
update your app's Info.plist with a CFBundleURLTypes entry
implement -application:didFinishLaunchingWithOptions: in your app delegate.
The second possibility is to write your own protocol handler. This works only within your app, but you can use it in conjunction with the technique described above. Use the method above to get the system to launch your app for a given URL, and then use a custom URL protocol handler within your app to leverage the power of iOS's URL loading system:
Create a your own subclass of NSURLProtocol.
Override +canInitWithRequest: -- usually you'll just look at the URL scheme and accept it if it matches the scheme you want to handle, but you can look at other aspects of the request as well.
Register your subclass: [MyURLProtocol registerClass];
Override -startLoading and -stopLoading to start and stop loading the request, respectively.
Read the NSURLProtocol docs linked above for more information. The level of difficulty here depends largely on what you're trying to implement. It's common for iOS apps to implement a custom URL handler so that other apps can make simple requests. Implementing your own HTTP or FTP handler is a bit more involved.
For what it's worth, this is exactly how PhoneGap works on iOS. PhoneGap includes an NSURLProtocol subclass called PGURLProtocol that looks at the scheme of any URL the app tries to load and takes over if it's one of the schemes that it recognizes. PhoneGap's open-source cousin is Cordova -- you may find it helpful to take a look.
For the second option in your question:
http://myapp.com/events/3/
There was a new technique introduced with iOS 9, called Universal Links which allows you to intercept links to your website, if they are https://
https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html

Categories

Resources