After setting up an app for deep linking. by creating an activity to launch when the link is opened in the browser. How do I go about getting the link that the browser used to start the intent?
Let us say the app catches the URL: "https://example.com/foo/bar?param=1&data=extra".
In the Activity launched I want to use the URL which opened the app. I am thinking maybe something like int the activity perform
Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
I dont know the variable to access the lauch URL from Uri data
It turns out I was on the right track all I do is
data.getQuery();
To get the last part of "https://example.com/foo/bar?param=1&data=extra" ie "param=1&data=extra"
Otherwise, call another function of data like.function() to get other aspects of the URI eg the whole URL that led to the opening of the activity
Stackoverflow makes you think half the time I always find the answer Immediately after I post a question.
The situation:
You have an extensive mobile website, m.somewhere.com
On Google Play you have an Android App that duplicates the key features of m.somewhere.com, but not all of them.
Your Client/Employer/Investor has asked you to implement deep-linking for those urls that can be handled by the app.
TL;DR - how do you implement this?
My Approach So Far:
First instinct: match only certain urls and launch for them. Problem: paucity of expression in the AndroidManifest intent-filter prevents this (e.g. http://weiyang.wordpress.ncsu.edu/2013/04/11/a-limitation-in-intent-filter-of-android-application/).
As a subset of the problem, suppose the server at m.somewhere.com knows that any url that ends in a number goes to a certain page on the site, and the marketing guys are constantly futzing with the seo, so e.g.
I want to launch the app for:
http://m.somewhere.com/find/abc-12345
https://m.somewhere.com/shop/xyz-45678928492
But not for
http://m.somewhere.com/find/abc-12345-xyz
https://m.somewhere.com/about-us
no combination of path, pathPrefix, or pathPattern will handle this.
Best practice on stackoverflow (Match URIs with <data> like http://example.com/something in AndroidManifest) seems to be to catch everything, and then handle the situation when you get to onCreate() and realize you shouldn't have handled this particular url:
Android 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:scheme="http"
android:host="m.somewhere.com"
android:pathPattern=".*"/>
</intent-filter>
...
Activity onCreate():
Intent i = getIntent()
String action = i.getAction();
Uri uri = i.getData();
if (Intent.ACTION_VIEW.equals(action) && cantHandleUrl(uri)) {
// TODO - fallback to browser.
}
I have programmed something similar to the above that is working, but it leads to a very bad end-user experience:
While browsing m.somewhere.com, there is a hiccup on every url click
while the app is launched and then falls back.
There is a nasty habit for a Chooser screen to popup for each and every link click on m.somewhere.com, asking the user which they would like to use (and the Android App is listed along with the browsers, but clicking on the Android App just launches the chooser screen again). If I'm not careful I get in an infinite relaunch loop for my app (if the user selects "Always"), and even if I am careful, it appears to the user that their "Always" selection is being ignored.
What can be done?
(EDIT: Displaying the site in a WebView in the app for unhandled pages is NOT an option).
Late answer, but for future readers: if you're supporting a minimum of API level 15 then there's a more direct (less hacky) way of falling back to a browser for URLs you realize you don't want to handle, without resorting to disabling/re-enabling URL catching components.
nbarraille's answer is creative and possibly your only option if you need to support APIs lower than 15, but if you don't then you can make use of Intent.makeMainSelectorActivity() to directly launch the user's default browser, allowing you to bypass Android's ResolverActivity app selection dialog.
Don't do this
So instead of re-broadcasting the URL Intent the typical way like this:
// The URL your Activity intercepted
String data = "example.com/someurl"
Intent webIntent = new Intent(Intent.ACTION_VIEW, data);
webIntent.addCategory(Intent.CATEGORY_BROWSABLE);
startActivity(webIntent);
Do this
You would broadcast this Intent instead:
Intent defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER);
defaultBrowser.setData(data);
startActivity(defaultBrowser);
This will tell Android to load the browser app and data URL. This should bypass the chooser dialog even if they have more than one browser app installed. And without the chooser dialog you don't have to worry about the app falling into an infinite loop of intercepting/re-broadcasting the same Intent.
Caveat
You have to be okay with opening the URL (the one you didn't want to handle) in the user's browser. If you wanted to give other non-browser apps a chance to open the link as well, this solution wouldn't work since there is no chooser dialog.
Pitfalls
As far as I can tell, the only quirk from using this solution is that when the user clicks one of your deep links, they'll get to choose to open in your app or their browser, etc. When they choose your app and your internal app logic realizes it's a URL it doesn't want to intercept, the user gets shown the browser right away. So they choose your app but get shown the browser instead.
NOTE: when I say "broadcast" in this answer, I mean the general term, not the actual Android system feature.
There is a somewhat hacky way of doing this:
In the manifest, create an intent-filter for m.somewhere.com, to open a specific deeplink handler activity.
In that Activity, figure out if your app supports that URL or not.
If it does, just open whatever activity
If it doesn't, send a non-resolved ACTION_VIEW intent to be opened by your browser. The problem here, is that your app will also catch this intent, and this will create an infinite loop if your app is selected as the default handler for that URL. The solution is to use PackageManager.setComponentEnabledSetting() to disable your deeplink handler Activity before you send that intent, and re-enable it after.
Some example code:
public class DeepLinkHandlerActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
Uri uri = intent.getData();
Intent intent = makeInternallySupportedIntent(uri);
if (intent == null) {
final PackageManager pm = getPackageManager();
final ComponentName component = new ComponentName(context, DeepLinkHandlerActivity.class);
pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
Intent webIntent = new Intent(Intent.ACTION_VIEW);
webIntent.setData(uri);
context.startActivity(webIntent);
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void[] params) {
SystemClock.sleep(2000);
pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
return null;
}
};
task.execute();
} else {
startActivity(intent);
}
finish();
}
}
Hope that helps.
Note: It looks like you need to delay the re-enabling by a couple of seconds for this to work.
Note 2: For a better experience, using a Transparent theme for your activity will make it look like your app didn't even open.
Note 3: If for some reason your app crashes or gets killed before the component re-registers, you're loosing deep link support forever (or until next update/reinstall), so I would also do the component re-enabling in App.onCreate() just in case.
URX provides a free tool (urxlinks.js) that automatically redirects mobile web users into an app if the app is installed. The documentation is available here: http://developers.urx.com/deeplinks/urx-links.html#using-urx-links-js
If two apps are using same scheme then the chooser screen will be popped as android wont know which app the link is intended for. Using custom scheme for your app might solve this issue. But still you can't be sure no one else will use that scheme.
It sounds like you're trying to treat your mobile app and mobile website as extensions of the same experience. That's good practice, generally speaking, but at this point the two are simply not at parity. At least until they reach parity I would not recommend automatically pushing the end user into your mobile app because users who are deliberately using the mobile site in order to find the content your app is missing will find this incredibly frustrating.
Instead, it might make sense to use a smart banner to encourage users on the mobile website pages that do have an in-app equivalent to open the app instead. Those banners would be your deeplinks. You could create them yourself or integrate a tool like Branch ( https://branch.io/universal-app-banner/ ) that handles deep linking and smart banners both.
That last part of your question has to do with where to place the deep links. One advantage to using smart banners instead of redirects is that you can embed them into the appropriate templates on your CMS instead of needing to rely on url detection.
Good luck!
This was my solution to your second problem. PackageManager.queryIntentActivities() will give you the list of apps/activities that would appear in the chooser. Iterate through the list (which should at least include the browser) and find an activity whose package name doesn't match the current app, and set the intent class name to it, then launch an Activity with that intent and call finish();
public Intent getNotMeIntent(Uri uri) {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
PackageManager manager = context.getPackageManager();
List<ResolveInfo> infos = manager.queryIntentActivities(intent, 0);
for (int i = 0; i < infos.size(); i++) {
ResolveInfo info = infos.get(i);
// Find a handler for this url that isn't us
if (!info.activityInfo.packageName.equals(context.getPackageName())) {
intent.setComponent(null);
intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
return intent;
}
}
// They have no browser
return null;
}
The Transparent theme (mentioned above) should be a good solution for the first problem.
In destination activity in onCreate set this code for Kotlin:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
val appLinkAction: String? = intent?.action
val appLinkData: Uri? = intent?.data
showDeepLinkData(appLinkAction, appLinkData)
}
private fun showDeepLinkData(appLinkAction: String?, appLinkData: Uri?) {
if (Intent.ACTION_VIEW == appLinkAction && appLinkData != null) {
val promotionCode = appLinkData.getQueryParameter("exampleQueryString")
Log.e("TAG", "Uri is: $appLinkData")
}
}
I have this code for checking app uninstall:
public void onReceive(Context context, Intent intent){
final String action = intent.getAction();
if("android.intent.action.PACKAGE_REMOVED".equals(action)){
// some action
}
Now I want get the start intent from the uninstalled app.
Is it possible?
Refer to following URLs:
Perform a task on uninstall in android
How to show an Activity BEFORE my app is uninstalled (Android)
The Post by Janusz is very helpful here..
Sadly android at the moment does not give you a possibility to perform code at the moment your app is uninstalled.
All the settings that are set via the SharedPreferences are deleted together with everything in the Aplication Data an Cache folder.
The only thing that will persist is the data that is written to the SD-Card and any changes to phone settings that are made. I don't know what happens to data that is synchronized to the contacts through your app.
I guess the only way to discover this is to test this. You can use the following code to find the launch intent of an application:
final Intent launchIntent = pm.getLaunchIntentForPackage(packageName);
where pm - is PackageManager.
To my point of view this is impossible and you'll receive launchIntent equal to null. But you should check this on your own.
Can we know that user has set default application for particular action? i. e. android.intent.action.CALL_PRIVILEGED
Suppose I my application also provide called on action of Call_privilaged. but user has set inbuilt dialer as default launcher for Call_privilaged action.
My question is can I know pro grammatically that user has set dialer as default launcher for Call_privalged action.
Thank You.
Can we know that user has set default application for particular action? i. e. android.intent.action.CALL_PRIVILEGED
I do not think that there is an easy way to do this. Calling getPreferredActivities() on PackageManager, and sifting through the List<IntentFilter> you get back to try to find a match for your Intent might work.
You can use resolveActivity() of Intent or PackageManager.
Intent intent = ...
ComponentName componentName = intent.resolveActivity(getPackageManager());
if (componentName.getPackageName().equals("android")) {
// No default selected
...
} else if (componentName.getPackageName().equals(getPackageName())) {
// We are default
...
} else {
// Someone else is default
...
}
If you don't handle the intent yourself you could also need a null check for the case where there is no app able to handle the intent.
Not sure if this works on all devices and all versions of Android. Tested on Android 4.1-4.3 on Nexus devices.
I'm working on an app and I want to integrate the Last.fm app into it. Basically, when someone is looking at an artist in my app, I would like to have a button that they can tap to open up Last.fm application with the artist's information.
This intent works, but it loads a menu asking which app I would like to use (Browser or Last.fm):
Intent i = new Intent();
i.setData(Uri.parse("http://last.fm/music/" + headliner));
i.setAction("android.intent.action.VIEW");
startActivity(i);
However, I just want to start the Last.fm app and skip the dialog asking which app to use, I thought maybe using the setPackage() method would work like this:
i.setPackage("fm.last.android");
But it causes the app to crash:
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=http://last.fm/music/Rihanna pkg=fm.last.android }
Is it possible to just start the Last.fm app? Here's a copy of Last.fm's AndroidManifest.xml for reference.
Thanks for reading,
Tony
Yes, it's possible but you need to know the correct component name. Launch the last.fm app regularly and check the logfile for the cmp=... information that's been used when the app is started. Use this as well in your app then.
I start the Z-DeviceTest app from the market from within my app without a problem like this:
final Intent intentDeviceTest = new Intent("android.intent.action.MAIN");
intentDeviceTest.setComponent(new ComponentName("zausan.zdevicetest","zausan.zdevicetest.zdevicetest"));
startActivity(intentDeviceTest);
in my case the info I took from the logcat was:
// dat=content://applications/applications/zausan.zdevicetest/zausan.zdevicetest.zdevicetest
// cmp=zausan.zdevicetest/.zdevicetest
in order to know how to start the app with the right component/class... do the same for the last.fm app
Edit:
I've tested to launch Last.fm from my own app, and this works fine without any errors:
final Intent intentDeviceTest = new Intent("android.intent.action.MAIN");
intentDeviceTest.setComponent(new ComponentName("fm.last.android","fm.last.android.LastFm"));
startActivity(intentDeviceTest);