App links intent filters in assetlinks.json not working on Android

24,031

Solution 1

For us it was Windows line endings!

Testing with "https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://domain1:port&relation=delegate_permission/common.handle_all_urls" proved invaluable as it gave us an "Could not parse statement list (not valid JSON)" error which led us to the problem.

TIP: It was good to use the 'Save File' button in the Android Studio App Links Assistant instead of copying and pasting as we did - that way it generates the file itself and is guaranteed not to have this issue.

Solution 2

There are some common pitfalls which you should check twice (I don't say that you did it wrong. It is just a check list):

  1. Verify that the assetlinks.json is valid and stored accessible from https://example.com/.well-known/assetlinks.json to do that you need to visit https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site= https://example.com&relation=delegate_permission/common.handle_all_urls, there must be no errors.
  2. If you link multiple domains at once, check that all domains are setup correctly as in step 1.
  3. Make sure that those <intent-filters> which contain your <data> tags have the attribute android:autoVerify="true".
  4. Verify that you have the required <meta-data> tag in your <application> tag:

    <meta-data
        android:name="asset_statements"
        android:resource="@string/asset_statements"/>
    

    The content of the asset_statements string must be:

    <string name="asset_statements" translatable="false">[{\"include\": \"https://example.com/.well-known/assetlinks.json\"}]
    
  5. Use for debug also the release signing certificate (don't be scared you cannot upload it accidentally) use this in your build.gradle:

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
        debug {
            debuggable true
            signingConfig signingConfigs.release
        }
    }
    

Solution 3

Looking at j__m comment, I found this.

  1. In AndroidManifest write so:

     <intent-filter android:autoVerify="true">
         <action android:name="android.intent.action.VIEW" />
    
         <category android:name="android.intent.category.DEFAULT" />
         <category android:name="android.intent.category.BROWSABLE" />
    
         <!-- Write <data> tags with one attribute, if you use several domains. -->
         <data android:scheme="https" />
         <data android:host="example.com" />
     </intent-filter>
     <!-- Other domains-->
     <intent-filter android:autoVerify="true">
         <action android:name="android.intent.action.VIEW" />
    
         <category android:name="android.intent.category.DEFAULT" />
         <category android:name="android.intent.category.BROWSABLE" />
    
         <data android:scheme="https" />
         <data android:host="server.com" />
     </intent-filter>
    

android:autoVerify="true" is needed for App Linking.

  1. Create assetlinks.json using Tools > App Links Assistant. Then press Open Digital Asset Links File Generator, enter domain, application id, select release signing config and press Generate Digital Asset Links File. Then you can save file or copy to clipboard.

  2. You can create several assetlinks.json files (for several applications) and join them into one JSON. To my mind it doesn't depend on Windows line endings (I used Notepad to concatenate JSONs). First time I auto-formatted it with Ctrl + Alt + L, and after uploading to domains App Link didn't work (probably because of later errors in AndroidManifest), so on the 2nd try I didn't format JSON. I created assetlinks.json for release and debug builds of the application.

  3. Upload assetlinks.json to https://example.com/.well-known/assetlinks.json (in this answer I write: example.com and mean your domain like company.name). Check it with https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://example.com&relation=delegate_permission/common.handle_all_urls. The file and the domain have some restrictions. In my case everything was simple, we didn't change settings.

  4. In your DeepLinkActivity you can parse URLs with regular expressions. Use JUnit to create tests. Call this method from onCreate():

     private fun processDeepLink() {
         if (intent?.data?.isHierarchical == true) {
             val data = intent?.dataString
             if (intent?.action == Intent.ACTION_VIEW && data != null) {
                 when {
                     REGEX.matches(data) -> // Get id and open some screen.
                     else -> // Either open MainActivity or skip this URL (open web browser instead).
                 }
                 finish()
             }
         }
     }
    
     companion object {
         val REGEX = "^https://example.com/some_request/(\\d+).*".toRegex()
     }
    

Warning! If you open a web browser from the application, you will fall into a loop. When clicking a link to your domain in the application, a browser won't appear, but your application will be opened automatically! What a surprise! So, in processDeepLink you should check the URL and open MainActivity when URL matches one of your masks. Skip others. Now a user will see a dialog with a list of browsers and your application (like in Deep Link). This happens because your application also handles links to your domains, like a browser.

You can also use WebView instead of a browser (not a good solution), open Chrome Custom Tabs or Chrome.

  1. Use device with Android 6 or later.

  2. If https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://example.com&relation=delegate_permission/common.handle_all_urls returned no errors, build the application. Create an Email message, SMS, QR-code or another application with a link to your domain. Click it, and App Link will open your application or Deep Link will show a dialog to choose an application. If App Link didn't work, read later.

  3. In LogCat select No Filters and type IntentFilter into search box. There should be:

     I/IntentFilterIntentOp: Verifying IntentFilter. verificationId:2 scheme:"https" hosts:"example.com" package:"com.my_package".
     I/IntentFilterIntentOp: Verification 0 complete. Success:true. Failed hosts:.
    

Probably you will get:

I/IntentFilterIntentOp: Verifying IntentFilter. verificationId:0 scheme:"https" hosts:"example.com server.com" package:"com.my_package".
I/IntentFilterIntentOp: Verification 0 complete. Success:false. Failed hosts:server.com.
  1. Later you will try to fix domains in the application, so sometimes you can launch for clean install:

     adb shell pm clear com.android.statementservice
    
  2. Start adb shell dumpsys package d and find your domains. There should be:

    Package Name: com.my_package
    Domains: example.com server.com
    Status:  always : 200000000
    

But probably it will be:

Package Name: com.my_package
Domains: example.com server.com
Status: ask

See also https://chris.orr.me.uk/android-app-linking-how-it-works/. Strange, but in an emulator it wrote: always, while App Link didn't work.

I also tried adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://example.com" to test App Link without a browser, but later it didn't work.

  1. If you have several domains, comment (or remove) other domains in AndroidManifest (retain only one domain, for example, "example.com"). Then click a URL https://example.com/something and check it uses App Link. In my case I checked release and debug builds of the application. While the debug build worked with App Link, release didn't (and sometimes vise versa). I used the solution of rekire:

    <meta-data
        android:name="asset_statements"
        android:resource="@string/asset_statements"/>
    

and it helped for 2 domains, but later stopped, so I removed it. In the end I wrote in AndroidManifest <data> tags with one attribute as j__m said.

Even if only one domain was failing, App Link was not working for other domains either. You can check domains one by one retaining only one domain each time in AndroidManifest.

See also http://androidideas.com/handling-app-links-in-android/, https://willowtreeapps.com/ideas/a-better-user-experience-for-deep-linking-on-android, https://developer.android.com/training/app-links/verify-site-associations,

Solution 4

For me, it came down to checking all the basics:

  1. Verify my assetLinks file is good with this tool: (replace domain1:port with your domain) https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://domain1:port&relation=delegate_permission/common.handle_all_urls
  2. Always test with a signed APK
  3. Make sure the test device is running Android 6.0 or later (this is the one that bit me because I forgot it - on older versions of android, you always get the user prompt)

Solution 5

Update

So I resolved my issue. Not sure which one did it (might have been a combination), but this is what I did:

  • Uninstalled "Google Play Services for Instant Apps": I had previously tinkered around with Instant Apps, so I thought maybe some old configurations might be sticking around like the debug package name, however this is unlikely.
  • Stopped using proxies: Proxies are helpful for debugging network calls, but HTTP/2 might not be fully supported on the tools I'm using.
  • Delete intent filter for legacy subdomains: This is the big one. One of my subdomains has been deprecated and is no longer available. In the AndroidManifest, if you have multiple hostnames declared for an activity that contains at least one autoVerify intent filter, each host is checked for the Digital Asset Link JSON file. If autoVerify fails for even one of the hosts, then none of the hosts are auto verified.

Original

When I first faced this issue, it was because my network was blocking calls to Google's servers for verifying the app links.

As OP and other answers have touched on, in general the API call to endpoint:

digitalassetlinks.googleapis.com

must be successful to bypass the chooser dialog. This is the web call the Android system makes, to verify the Digital Asset Link JSON file, and seems to be made upon app install/update. A helpful place to look is the Logcat, searching for items with the text "I/SingleHostAsyncVerifier:". If you see "--> true" at the end of the log, your app

Lately though, these calls have been failing for me due to what appears to be some bug which may have been introduced recently. The device is receiving this response from the API call above:

Error: unavailable: Wrong content type in HTTP response headers while fetching statements from {host}/.well-known/assetlinks.json (which is equivalent to '{host}/.well-known/assetlinks.json'): expected 'Content-Type: application/json' but found text/html [11] while fetching Web statements from {host}./.well-known/assetlinks.json

It's been awhile since I last looked at these requests, so I don't remember what they looked like before. But it seems possible that there may have been some recent update involving App Links or the Android networking framework, where they switched over to protocol buffers for this feature (and forgot to support it in another one).

Another indication that things may have changed, is that the request path today appears different from the ones mentioned in the previous answers:

https://digitalassetlinks.googleapis.com/google.digitalassetlinks.v1.AssetLinks/Check

Share:
24,031

Related videos on Youtube

shadowcursor
Author by

shadowcursor

Updated on January 27, 2022

Comments

  • shadowcursor
    shadowcursor over 2 years

    My app defines the intent filters to handle URL's from my site defined by

    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW"/>
      <category android:name="android.intent.category.DEFAULT"/>
      <category android:name="android.intent.category.BROWSABLE"/>
      <data android:host="www.host.com" android:scheme="http"/>
    </intent-filter>
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW"/>
      <category android:name="android.intent.category.DEFAULT"/>
      <category android:name="android.intent.category.BROWSABLE"/>
      <data android:host="www.host.com" android:scheme="https"/>
    </intent-filter>
    

    The app correctly detects URL's of the correct host but queries the user whether to open them in the app or browser. I tried using the App links verification as specified here: https://developer.android.com/training/app-links/index.html

    As seen in my server logs, when installing the app, the device queries /well-known/assetlinks.json and it responds with a 200 status. Testing the digital assets file using the

    https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://<domain1>:<port>&relation=delegate_permission/common.handle_all_urls

    API and it found no errors.

    The SHA256 in the assetlinks.json file was obtained using

    keytool -list -v -keystore my-release-key.keystore 
    

    the same .keystore of which the app was signed.

    Running adb shell dumpsys package d returns that the link verification status is "ask" meaning that verification failed. Why might verification be failing?

    • Pruthvi
      Pruthvi almost 5 years
      Hi @mohamed.ahmed were you able to solve this? I am having kind of similar issue. My android app link works for signed apk before uploading to the play store. stops working after taking it to live in the play store. refer the link for more info stackoverflow.com/questions/57959217/…
    • linhadiretalipe
      linhadiretalipe over 3 years
      Maybe you are put in your assets file (in server) the SHA256 from you local certificate. You need put the SHA256 from playGoogle. stackoverflow.com/a/61204765/496637
  • Beemo
    Beemo about 6 years
    Also, I would add that the app links won't work if you have the same root host with different path values pointing to the same activity (if you implement it with .well-known process). I needed this to test my feature on my test servers and on production at the same time.
  • Brian Rak
    Brian Rak about 6 years
    This answer got me headed down the right path, but for me, it was actually the byte-order mark (BOM), not the line endings. I removed the BOM and the file worked just fine with Windows line endings. Please see my answer for details on how to remove the BOM.
  • MadMac
    MadMac over 4 years
    I cut and pasted it into notepad first and it fixed the issue for me. Could not find a Save File button.
  • CoolMind
    CoolMind over 4 years
    How will @string/asset_statements help? Especially if it has only one URL, while in AndroidManifest we have several.
  • CoolMind
    CoolMind over 4 years
    Strange, but it helped when I added both domains (from AndroidManifest) to asset_statements. But later again didn't work, so I removed <meta-data>.
  • Shyam Sunder
    Shyam Sunder over 2 years
    Thanks a lot, this pointed right to the issue.