WICG / attribution-reporting-api

Attribution Reporting API
https://wicg.github.io/attribution-reporting-api/
Other
371 stars 173 forks source link

Documenting supported app<->web attribution flows #545

Open johnivdel opened 2 years ago

johnivdel commented 2 years ago

The app_to_web explainer doesn't explicitly document what conversion flows are supported on a single device, we should consider documenting explicit use-cases and how they are supported by this API.

The following table shows for impression conversion/events occurring in various contexts, what APIs are responsible for registering the reports. Rows indicate where the impression occurred, columns indicate where the conversion occurred, and table cells indicate which API is used to register.

In this table, all reports are handled by Android.

"Web" is the web API which registers with the underlying operating system described in app_to_web.md, and "OS" refers to the native API proposed by Android.

Browser App Webview (browser)
Browser Web, Web Web, OS Web, Web
App OS, Web OS, OS OS, Web
WebView (rendered ad) OS, Web OS, OS OS, Web
WebView (browser) Web, Web Web, OS Web, Web

"Browser" includes various interfaces for the browser app, for example CCT in the case of Google Chrome as discussed in #239.

"WebView (rendered ad)" refers to ads where the content was rendered using a Webview, but the ad was served by the app.

A flow which doesn't fall into these categories would be:

  1. ad is displayed within an app
  2. ad is clicked
  3. ad link opens the browser
  4. impressions are registered within the browser as part of opening the link

In this case, the source event (user clicking the ad) occurred in a native app which would require the app to register the event directly with the OS. Whereas the browser is responsible for doing so today.

If there are any other flows which don't fall into these categories, we should document them as well.

johnivdel commented 2 years ago

It is also possible that an ad impression and a click may happen in a different context from where the link is opened.

Consider the following example:

In this case, the app will need to register the ad impression with the Android system API directly. However, because the click happened within the Webview, the app will need to be sure to track InputEvents in order to properly register a click with API.

Here is one example of how that could be achieved:

MainActivity.kt

import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

  @SuppressLint("SetJavaScriptEnabled")
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val webView: WebView = requireViewById(R.id.webview)
    webView.settings.javaScriptEnabled = true
    webView.webViewClient = EventLoggingWebViewClient()
    webView.loadUrl("https://publisher.example")
  }

  class EventLoggingWebViewClient : WebViewClient() {
    private val tag = "LastEventWebViewClient"

    override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
      Log.i(tag, request?.url.toString())
      if (view is EventCapturingWebView){
        Log.i(tag, "Captured events:")
        for (event in view.lastTouch()){
          Log.i(tag, event.toString())
        }
      }

      // App may register with the Attribution Reporting API directly, or pass 
      // view.lastTouch().get(view.lastTouch().size() - 1) elsewhere to register.

      return super.shouldOverrideUrlLoading(view, request)
    }
  }
}

EventCapturingWebView.kt

import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
import android.webkit.WebView
import java.util.LinkedList

class EventCapturingWebView @JvmOverloads constructor(
  context: Context, attrs: AttributeSet? = null
) : WebView(context, attrs) {

  private val tag = "EventCapturingWebView"

  private val eventBuffer = LinkedList<MotionEvent>()

  @SuppressLint("ClickableViewAccessibility")
  override fun onTouchEvent(event: MotionEvent?): Boolean {
    if (event != null) {
      if (event.action == ACTION_DOWN) {
        // Reset the event buffer on each ACTION_DOWN (press)
        eventBuffer.clear()
      }
      // Obtain a clone of the event, since the framework reuses the event it passes
      // to this method
      val newEvent = MotionEvent.obtain(event)
      eventBuffer.add(newEvent)
    }
    return super.onTouchEvent(event)
  }

  fun lastTouch() : List<MotionEvent> {
    @Suppress("UNCHECKED_CAST")
    return eventBuffer.clone() as List<MotionEvent>
  }

}