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
Related
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_.
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.
I want to register an intent-filter exclusively for the share in the Youtube app.
So far I'm able to receive the intent from Youtube successfully. The problem is my intent filer is not specific enough. My app is displayed as available for other share features in other apps (not only for Youtube).
This is what I'm using right now:
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
I have reviewed several questions (most are much like this one) the problem is there is an inaccuracy in those types of answers:
<data android:host="www.youtube.com" ... />
According to the data documentation the scheme most be provided in order for the host to be valid. So in those answers simply adding the host, doesn't make the intent-filter specific for Youtube, because there is no scheme, therefore, the host is ignored.
So I have been trying to figure this out by using the available methods of the intent when the Activity is started:
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
for (String key : bundle.keySet()) {
Log.d("KEY", key);
}
//The above loop will log
//... D/KEY: android.intent.extra.SUBJECT
//... D/KEY: android.intent.extra.TEXT
//This is are the same keys than above, but using the available constants
String subject = getIntent().getStringExtra(Intent.EXTRA_SUBJECT);
String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
//The subject is the video title
Log.d("SUBJECT", subject);
//The text is the video url, example: https://youtu.be/p6qX_lg4wTc
Log.d("TEXT", text);
//Action is consistent with the intent-filter android.intent.action.SEND
Log.d("ACTION", intent.getAction());
//This is consistent with the intent-filter data mime type text/plain
Log.d("TYPE", intent.getType());
/*
This is the problem.
The scheme is null (that is why I'm using String value of).
*/
Log.d("scheme", String.valueOf(intent.getScheme()));
So, when the available information in the intent is checked, everything seems to be in order, but not the scheme. So, based on what is gotten, I have done some blind attempts to figure it out:
<data android:scheme="http" android:mimeType="text/plain"/>
//I'm adding youtu.be here because is the url format in the text extra
<data android:scheme="http" android:host="youtu.be" android:mimeType="text/plain"/>
Adding http or https won't work, it makes the app is no longer available in the chooser. This means it will neither work the other attempt adding the host.
Doe's anybody knows how to create an intent-filter exclusively for Youtube share?
PS: I know I could validate the url to see if it match a Youtube url, but having my app in every chooser matching SEND doesn't seem user friendly
tldr: You can't make an intent-filter exclusively for Youtube app, the intent for sharing a video doesn't have anything to narrow it down.
So I got really obsessed about this, the only way to find out seems to check the Youtube app code. I found the apk here and then decompile with this. I think I found the code in this file (the text error seems to confirm my finding).
So, if my deductions are correct, then what is going on is Youtube app make an intent with no scheme or nothing else to make it specific. It only uses SEND.
This not answer your specific question, but I think achieves the same you want.
You can use the youtube android player api library and the YouTubeIntents
Like this:
Intent youtubeIntent =
YouTubeIntents.createPlayVideoIntent(getApplicationContext(), VIDEO_ID);
startActivity(youtubeIntent);
I am facing a problem in override the On Click Behavior in Appboy deeplink
Please find the following data
1- Register Appboy in BaseActivity which is the parent activity for all Application Activities
#Override
protected void onResume() {
AppboyInAppMessageManager.getInstance().registerInAppMessageManager(this);
Appboy.getInstance(this).requestInAppMessageRefresh();
}
#Override
protected void onPause() {
AppboyInAppMessageManager.getInstance().unregisterInAppMessageManager(this);
}
2- Add the receivers in Manifest File as following
<receiver android:name="com.forsale.forsale.appboy.AppboyGcmReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="com.forsale.forsale" />
</intent-filter>
</receiver>
<receiver
android:name="com.forsale.forsale.appboy.AppBoyOpenReceiver"
android:exported="false" >
<intent-filter>
<action android:name="com.forsale.forsale.intent.APPBOY_PUSH_RECEIVED" />
<action android:name="com.forsale.forsale.intent.APPBOY_NOTIFICATION_OPENED" />
</intent-filter>
</receiver>
Know I can send in app message using app boy dashboard, and receive the message, but when I click the message it open appboy web activity with the link
I need to override this behaviour to be able to get the link that I sent in In app message and parse some parameters from it and direct the use to an activity inside my app
I have tried the following
remove default app boy web activity from manifest file /// the app crash
implement the IInAppMessageManagerListener /// the app stop receiving any messages
Please note that the application call the onReceive method when trying to register appboy and print the log (action = REGISTRATION, RegId = "..."), but it never lo any other actions like RECEIVE, or OPEN
public void onReceive(Context context, Intent intent) {
AppboyLogger.i("AMIRA", String.format("Amira %s", intent.toString()));
String action = intent.getAction();
AppboyLogger.i("AMIRA", String.format("Amira %s", action));
Bundle bundle = intent.getExtras();
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
AppboyLogger.i("AMIRA", String.format("Amira %s", key + ":" + value.toString()));
}
}
The root of the problem is that we differentiate deep links and http links based on schema of the URI, so http (and some other schemes) links are detected as web links, and other formats are seen as deep links (see https://github.com/Appboy/appboy-android-sdk/blob/master/android-sdk-ui/src/com/appboy/ui/actions/ActionFactory.java).
We’ll consider how to instrument things for the use case you have, but in the meantime there’s a couple of ways you could solve the issue:
1) Create a deep link that is not also an http link. Everything should work if your link instead looks like, for example, forsale://mylink?a=b&2=3....etc.
2) Set a custom in-app message manager listener: https://documentation.appboy.com/Android/#in-app-message-customization. You can see an example of how we do this in our Droidboy sample app. In your case, you’d want to return defaults for everything but onInAppMessageButtonClicked and onInAppMessageClicked where you’d want to handle the link yourself if it’s of the format of your deep link. Your ticket indicates you’ve tried this, but I’d suggest starting with "the default one we create in the AppboyInAppMessageManager.java (#L608) in the Android SDK - and then just modifying the *clicked methods.
3) Download our UI code and modify the source. You could optionally download the Appboy Android SDK and modify the ActionFactory to handle your deep link in the way you want. Though, at the point you are going to do something like this, solution #2 is likely going to be a nicer one to implement and maintain.
Please let us know if one of these solutions works for you and if you have any other comments/questions.
Thanks,
Waciuma
Say I want to define that an URI such as:
myapp://path/to/what/i/want?d=This%20is%20a%20test
must be handled by my own application, or service. Notice that the scheme is "myapp" and not "http", or "ftp". That is precisely what I intend: to define my own URI schema globally for the Android OS. Is this possible?
This is somewhat analogous to what some programs already do on, e.g., Windows systems, such as Skype (skype://) or any torrent downloader program (torrent://).
This is very possible; you define the URI scheme in your AndroidManifest.xml, using the <data> element. You setup an intent filter with the <data> element filled out, and you'll be able to create your own scheme. (More on intent filters and intent resolution here.)
Here's a short example:
<activity android:name=".MyUriActivity">
<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="path" />
</intent-filter>
</activity>
As per how implicit intents work, you need to define at least one action and one category as well; here I picked VIEW as the action (though it could be anything), and made sure to add the DEFAULT category (as this is required for all implicit intents). Also notice how I added the category BROWSABLE - this is not necessary, but it will allow your URIs to be openable from the browser (a nifty feature).
Complementing the #DanielLew answer, to get the values of the parameteres you have to do this:
URI example: myapp://path/to/what/i/want?keyOne=valueOne&keyTwo=valueTwo
in your activity:
Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
String valueOne = uri.getQueryParameter("keyOne");
String valueTwo = uri.getQueryParameter("keyTwo");
}
I strongly recommend that you not define your own scheme. This goes against the web standards for URI schemes, which attempts to rigidly control those names for good reason -- to avoid name conflicts between different entities. Once you put a link to your scheme on a web site, you have put that little name into entire the entire Internet's namespace, and should be following those standards.
If you just want to be able to have a link to your own app, I recommend you follow the approach I described here:
How to register some URL namespace (myapp://app.start/) for accessing your program by calling a URL in browser in Android OS?
Another alternate approach to Diego's is to use a library:
https://github.com/airbnb/DeepLinkDispatch
You can easily declare the URIs you'd like to handle and the parameters you'd like to extract through annotations on the Activity, like:
#DeepLink("path/to/what/i/want")
public class SomeActivity extends Activity {
...
}
As a plus, the query parameters will also be passed along to the Activity as well.
As the question is asked years ago, and Android is evolved a lot on this URI scheme.
From original URI scheme, to deep link, and now Android App Links.
Android now recommends to use HTTP URLs, not define your own URI scheme. Because Android App Links use HTTP URLs that link to a website domain you own, so no other app can use your links. You can check the comparison of deep link and Android App links from here
Now you can easily add a URI scheme by using Android Studio option: Tools > App Links Assistant.
Please refer the detail to Android document: https://developer.android.com/studio/write/app-link-indexing.html