המדריך למפתחים של Attribution Reporting API

במהלך הקריאה של המסמכים בנושא ארגז החול לפרטיות ב-Android, כדאי להשתמש בלחצן תצוגה מקדימה למפתחים או בטא כדי לבחור את גרסת התוכנה שאתם עובדים איתה, כי ההוראות עשויות להיות שונות.


המטרה של Attribution Reporting API היא לבטל את ההסתמכות על מזהי משתמשים של צדדים שונים כדי לשפר את פרטיות המשתמשים, ולספק תמיכה בתרחישי שימוש עיקריים בשיוך (Attribution) ובמעקב המרות באפליקציות. במדריך למפתחים הזה מוסבר איך להגדיר ולבדוק את ממשקי ה-API של Attribution Reporting כדי לרשום קליקים על מודעות, צפיות והמרות על ידי קריאה לשיטות שרושמות את הטריגרים והמקורות הרלוונטיים לאירועים כאלה.

במדריך הזה נסביר איך להגדיר נקודות קצה של שרתים וליצור אפליקציית לקוח שקוראת לשירותים האלה. במסמך הצעת העיצוב מפורט מידע נוסף על העיצוב הכולל של Attribution Reporting API.

מונחי מפתח

  • מקורות השיוך מתייחסים לקליקים או לצפיות.
  • טריגרים הם אירועים שאפשר לשייך להמרות.
  • דוחות מכילים נתונים על טריגר ומקור השיוך התואם. הדוחות האלה נשלחים בתגובה לאירועי טריגר. ‫Attribution Reporting API תומך בדוחות ברמת האירוע ובדוחות שניתן לצבור.

לפני שמתחילים

כדי להשתמש ב-Attribution Reporting API, צריך לבצע את המשימות בצד השרת ובצד הלקוח שמפורטות בקטעים הבאים.

הגדרת נקודות קצה של Attribution Reporting API

‫Attribution Reporting API דורש קבוצה של נקודות קצה שאפשר לגשת אליהן ממכשיר בדיקה או מאמולטור. יוצרים נקודת קצה אחת לכל אחת מהמשימות הבאות בצד השרת:

יש כמה שיטות להגדרת נקודות הקצה הנדרשות:

  • הדרך המהירה ביותר להתחיל היא לפרוס את הגדרות השירות של OpenAPI v3 ממאגר קוד לדוגמה שלנו בפלטפורמה של מיקרו-שירותים או בפלטפורמה מדומה. אפשר להשתמש ב-Postman, ב-Prism או בכל פלטפורמה אחרת של שרת מדומה שתומכת בפורמט הזה. מפעילים כל נקודת קצה ועוקבים אחרי מזהי ה-URI לשימוש באפליקציה. כדי לאמת את מסירת הדוח, אפשר לעיין בקריאות שבוצעו בעבר לפלטפורמה המדומה או לפלטפורמה ללא שרת.
  • מריצים שרת עצמאי משלכם באמצעות דוגמת Kotlin שמבוססת על Spring Boot. פורסים את השרת הזה אצל ספק שירותי הענן או בתשתית הפנימית.
  • אפשר להשתמש בהגדרות השירות כדוגמאות לשילוב נקודות הקצה במערכת הקיימת.

אישור הרשמת המקור

אפשר לפנות לנקודת הקצה הזו מ-URI שדומה לזה:

https://adtech.example/attribution_source

כשאפליקציית לקוח רושמת מקור שיוך, היא מספקת את ה-URI של נקודת הקצה הזו בשרת. לאחר מכן, Attribution Reporting API שולח בקשה וכולל אחת מהכותרות הבאות:

  • לאירועים מסוג קליק:

    Attribution-Reporting-Source-Info: navigation
    
  • לאירועי צפייה:

    Attribution-Reporting-Source-Info: event
    

מגדירים את נקודת הקצה של השרת כך שתגיב עם הפרטים הבאים:

// Metadata associated with attribution source.
Attribution-Reporting-Register-Source: {
  "destination": "[app package name]",
  "web_destination": "[eTLD+1]",
  "source_event_id": "[64 bit unsigned integer]",
  "expiry": "[64 bit signed integer]",
  "event_report_window": "[64-bit signed integer]",
  "aggregatable_report_window": "[64-bit signed integer]",
  "priority": "[64 bit signed integer]",
  "filter_data": {
    "[key name 1]": ["key1 value 1", "key1 value 2"],
    "[key name 2]": ["key2 value 1", "key2 value 2"],
    // Note: "source_type" key will be automatically generated as
    // one of {"navigation", "event"}.
  },
  // Attribution source metadata specifying histogram contributions in aggregate
  // report.
  "aggregation_keys": {
    "[key1 name]": "[key1 value]",
    "[key2 name]": "[key2 value]",
  },

    "debug_key": "[64-bit unsigned integer]",
    "debug_reporting": [boolean]
}
// Specify additional ad tech URLs to register this source with.
Attribution-Reporting-Redirect: <Ad Tech Partner URI 1>
Attribution-Reporting-Redirect: <Ad Tech Partner URI 2>

דוגמה עם ערכים לדוגמה:

Attribution-Reporting-Register-Source: {
  "destination": "android-app://com.example.advertiser",
  "source_event_id": "234",
  "expiry": "259200",
  "event_report_window": "172800",
  "aggregatable_report_window": "172800",
  "priority": "5",
  "filter_data": {
    "product_id": ["1234"]
  },
  "aggregation_keys": {
  // Generates a "0x159" key piece named (low order bits of the key) for the key
  // named "campaignCounts".
  // User saw an ad from campaign 345 (out of 511).
    "campaignCounts": "0x159",

  // Generates a "0x5" key piece (low order bits of the key) for the key named
  // "geoValue".
  // Source-side geo region = 5 (US), out of a possible ~100 regions.
    "geoValue": "0x5",
  },
  // Opts in to receiving verbose debug reports
  "debug_reporting": true
}

Attribution-Reporting-Redirect:
https://adtechpartner1.example?their_ad_click_id=567
Attribution-Reporting-Redirect:
https://adtechpartner2.example?their_ad_click_id=890

אם Attribution-Reporting-Redirects מכיל כתובות URI של שותפים לפרסום דיגיטלי, Attribution Reporting API שולח בקשה דומה לכל כתובת URI. כל שותף טכנולוגי לפרסום צריך להגדיר שרת שמגיב עם הכותרות הבאות:

Attribution-Reporting-Register-Source: {
  "destination": "[app package name]",
  "web_destination": "[eTLD+1]",
  "source_event_id": "[64 bit unsigned integer]",
  "expiry": "[64 bit signed integer]",
  "event_report_window": "[64-bit signed integer]",
  "aggregatable_report_window": "[64-bit signed integer]",
  "priority": "[64 bit signed integer]",
  "filter_data": {
    "[key name 1]": ["key1 value 1", "key1 value 2"],
    "[key name 2]": ["key2 value 1", "key2 value 2"],
    // Note: "source_type" key will be automatically generated as
    // one of {"navigation", "event"}.
  },
  "aggregation_keys": {
    "[key1 name]": "[key1 value]",
    "[key2 name]": "[key2 value]",
  }
}
// The Attribution-Reporting-Redirect header is ignored for ad tech partners.

אישור רישום של טריגר המרה

אפשר לפנות לנקודת הקצה הזו באמצעות URI שדומה לזה:

https://adtech.example/attribution_trigger

כשנרשם אירוע הפעלה באפליקציית לקוח, האפליקציה מספקת את ה-URI של נקודת הקצה הזו בשרת. לאחר מכן, Attribution Reporting API שולח בקשה וכולל אחת מהכותרות הבאות:

מגדירים את נקודת הקצה של השרת כך שתגיב עם הפרטים הבאים:

// Metadata associated with trigger.
Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    // "trigger_data returned" in event reports is truncated to
    // the last 1 or 3 bits, based on conversion type.
    "trigger_data": "[unsigned 64-bit integer]",
    "priority": "[signed 64-bit integer]",
    "deduplication_key": "[signed 64-bit integer]",
    // "filter" and "not_filters" are optional fields which allow configuring
    // event trigger data based on source's filter_data. They consist of a
    // filter set, which is a list of filter maps. An event_trigger_data object
    // is ignored if none of the filter maps in the set match the source's
    // filter data.
    // Note: "source_type" can be used as a key in a filter map to filter based
    // on the source's "navigation" or "event" type. The first
    // Event-Trigger that matches (based on the filters/not_filters) will be
    // used for report generation. If none of the event-triggers match, no
    // event report will be generated.
    "filters": [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from filters or source's filter_data, it won't be
      // used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }],
    "not_filters":  [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from not_filters or source's filter_data, it won't
      // be used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }]
  }],
  // Specify a list of dictionaries that generates aggregation keys.
  "aggregatable_trigger_data": [
    // Each dictionary entry independently adds pieces to multiple source keys.
    {
      "key_piece": "[key piece value]",
      "source_keys": ["[key name the key piece value applies to]",
      ["list of IDs in source to match. Non-matching IDs are ignored"]]
      // filters/not_filters are optional fields similar to event trigger data
      // filter fields.
      "filters": [{
        "[key name 1]": ["key1 value 1", "key1 value 2"]
      }],
      "not_filters":  [{
          "[key name 1]": ["key1 value 1", "key1 value 2"],
          "[key name 2]": ["key2 value 1", "key2 value 2"],
      }]
    },
    ..
  ],
  // Specify an amount of an abstract value which can be integers in [1, 2^16]
  // to contribute to each key that is attached to aggregation keys in the
  // order they are generated.
  "aggregatable_values": [
     // Each source event can contribute a maximum of L1 = 2^16 to the
     // aggregate histogram.
    {
     "[key_name]": [value]
    },
    ..
  ],
  aggregatable_deduplication_keys: [{
  deduplication_key": [unsigned 64-bit integer],
    "filters": {
        "category": [filter_1, …, filter_H]
      },
    "not_filters": {
        "category": [filter_1, …, filter_J]
      }
  },
  ...
  {
  "deduplication_key": [unsigned 64-bit integer],
    "filters": {
        "category": [filter_1, …, filter_D]
      },
    "not_filters": {
        "category": [filter_1, …, filter_J]
      }
    }
  ]

  "debug_key": "[64-bit unsigned integer]",
  "debug_reporting": [boolean]

}
// Specify additional ad tech URLs to register this trigger with.
// Repeated Header field "Attribution-Reporting-Redirect"
Attribution-Reporting-Redirect: <Ad Tech Partner URI 1>
Attribution-Reporting-Redirect: <Ad Tech Partner URI 2>

דוגמה עם ערכים לדוגמה:

Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    "trigger_data": "1122", // Returns 010 for CTCs and 0 for VTCs in reports.
    "priority": "3",
    "deduplication_key": "3344"
    "filters": [{ // Filter strings can't exceed 25 characters
      "product_id": ["1234"],
      "source_type": ["event"]
    }]
  },
  {
    "trigger_data": "4", // Returns 100 for CTCs and 0 for VTCs in reports.
    "priority": "3",
    "deduplication_key": "3344"
    "filters": [{ // Filter strings can't exceed 25 characters
      "product_id": ["1234"],
      "source_type": ["navigation"]
    }]
  }],
  "aggregatable_trigger_data": [
    // Each dictionary independently adds pieces to multiple source keys.
    {
      // Conversion type purchase = 2 at a 9-bit offset, i.e. 2 << 9.
      // A 9-bit offset is needed because there are 511 possible campaigns,
      // which takes up 9 bits in the resulting key.
      "key_piece": "0x400",// Conversion type purchase = 2
      // Apply this key piece to:
      "source_keys": ["campaignCounts"]
       // Filter strings can't exceed 25 characters
    },
    {
      // Purchase category shirts = 21 at a 7-bit offset, i.e. 21 << 7.
      // A 7-bit offset is needed because there are ~100 regions for the geo
      // key, which takes up 7 bits of space in the resulting key.
      "key_piece": "0xA80",
      // Apply this key piece to:
      "source_keys": ["geoValue", "nonMatchingIdsAreIgnored"]
      // source_key values must not exceed the limit of 25 characters
    }
  ],
  "aggregatable_values":
    {
      // Privacy budget for each key is L1 / 2 = 2^15 (32768).
      // Conversion count was 1.
      // Scale the count to use the full budget allocated: 1 * 32768 = 32768.
      "campaignCounts": 32768,

      // Purchase price was $52.
      // Purchase values for the app range from $1 to $1,024 (integers only).
      // Scaling factor applied is 32768 / 1024 = 32.
      // For $52 purchase, scale the value by 32 ($52 * 32 = $1,664).
      "geoValue": 1664
    }
  ,
  // aggregatable_deduplication_keys is an optional field. Up to 50 "keys"
  // can be included in the aggregatable_deduplication_keys list. Filters, not
  // filters, and deduplication_key are optional fields. If deduplication_key
  // is omitted, it will be treated as a null value. See
  // https://wicg.github.io/attribution-reporting-api/#triggering-aggregatable-attribution
  aggregatable_deduplication_keys:
  [
    {
    deduplication_key": 3,
        "filters": {
          "category": [A]
        }
    },
    {
    "deduplication_key": 4,
        "filters": {
          "category": [C, D]
        },
        "not_filters": {
          "category": [F]
        }
    }
  ]
  // Opts into receiving verbose debug reports
  "debug_reporting": true
}
Attribution-Reporting-Redirect:https://adtechpartner.example?app_install=567

יש מגבלה של 25 בייט לכל מזהה מפתח צבירה ולכל מחרוזת מסנן. המשמעות היא שמזהי מפתח הצבירה ומחרוזות המסננים לא יכולים להיות ארוכים מ-25 תווים. בדוגמה הזו, campaignCounts הוא מחרוזת של 14 תווים, ולכן הוא מזהה תקין של מפתח צבירה, ו-1234 הוא מחרוזת של 4 תווים, ולכן הוא מחרוזת מסנן תקינה. אם מזהה מפתח צבירה או מחרוזת מסנן חורגים מ-25 תווים, המערכת מתעלמת מהטריגר.

אם Attribution-Reporting-Redirect מכיל כתובות URI של שותפים לפרסום דיגיטלי, Attribution Reporting API שולח בקשה דומה לכל כתובת URI. כל שותף טכנולוגי לפרסום צריך להגדיר שרת שמגיב עם הכותרות הבאות:

// Metadata associated with trigger.
Attribution-Reporting-Register-Trigger: {
  "event_trigger_data": [{
    // "trigger_data" returned in event reports is truncated to
    // the last 1 or 3 bits, based on conversion type.
    "trigger_data": "[unsigned 64-bit integer]",
    "priority": "[signed 64-bit integer]",
    "deduplication_key": "[signed 64-bit integer]",
    // filter and not_filters are optional fields which allow configuring
    // different event trigger data based on source's filter_data. They
    // consist of a filter set, which is a list of filter maps. An
    // event_trigger_data object is ignored if none of the filter maps in the
    // set match the source's filter data. Note: "source_type" can be used as
    // a key in a filter map to filter based on the source's "navigation" or
    // "event" type. The first Event-Trigger that matches (based on the
    // filters/not_filters) will be used for report generation. If none of the
    // event-triggers match, no report will be generated.
    "filters": [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from filters or source's filter_data, it won't be
      // used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }],
    "not_filters":  [{
      "[key name 1]": ["key1 value 1", "key1 value 2"],
      // If a key is missing from not_filters or source's filter_data, it won't
      // be used during matching.
      "[key name 2]": ["key2 value 1", "key2 value 2"],
    }]
  }],
  "aggregatable_trigger_data": [
    // Each dictionary entry independently adds pieces to multiple source keys.
    {
      "key_piece": "[key piece value]",
      "source_keys": ["[key name the key piece value applies to]",
      ["list of IDs in source to match. Non-matching IDs are ignored"]],
      // filters/not_filters are optional fields similar to event trigger data
      // filter fields.
      "filters": [{
        "[key name 1]": ["key1 value 1", "key1 value 2"]
      }],
      "not_filters":  [{
          "[key name 1]": ["key1 value 1", "key1 value 2"],
          "[key name 2]": ["key2 value 1", "key2 value 2"],
      }]
    },
    ..
  ],
  // Specify an amount of an abstract value which can be integers in [1, 2^16] to
  // contribute to each key that is attached to aggregation keys in the order they
  // are generated.
  "aggregatable_values": [
    // Each source event can contribute a maximum of L1 = 2^16 to the aggregate
    // histogram.
    {
     "[key_name]": [value]
    }
  ]
}
// The Attribution-Reporting-Redirect header is ignored for ad tech partners.

אישור דוחות ברמת האירוע

אפשר לפנות לנקודת הקצה הזו באמצעות URI. מידע נוסף על רישום של URI זמין במאמר הרשמה לחשבון בארגז החול לפרטיות. (המערכת מסיקה את ה-URI מהמקור של השרתים שמשמשים לקבלת רישום המקור ורישום הטריגר). אם משתמשים במזהי ה-URI לדוגמה של נקודות קצה שמקבלות רישום של מקור ומקבלות רישום של טריגר, ה-URI של נקודת הקצה הזו הוא:

https://adtech.example/.well-known/attribution-reporting/report-event-attribution

מגדירים את השרת כך שיקבל בקשות JSON בפורמט הבא:

{
  "attribution_destination": "android-app://com.advertiser.example",
  "source_event_id": "12345678",
  "trigger_data": "2",
  "report_id": "12324323",
  "source_type": "navigation",
  "randomized_trigger_rate": "0.02"
   [Optional] "source_debug_key": "[64-bit unsigned integer]",
   [Optional] "trigger_debug_key": "[64-bit unsigned integer]",
}

מפתחות ניפוי באגים מאפשרים לקבל תובנות נוספות לגבי דוחות הייחוס. מידע נוסף על הגדרת מפתחות ניפוי באגים

אישור דוחות שניתן לצבור

אפשר לפנות לנקודת הקצה הזו באמצעות URI. מידע נוסף על רישום של URI זמין במאמר הרשמה לחשבון בארגז החול לפרטיות. (המערכת מסיקה את ה-URI מהמקור של השרתים שמשמשים לקבלת רישום המקור ורישום הטריגר). אם משתמשים במזהי ה-URI לדוגמה של נקודות קצה שמקבלות רישום של מקור ומקבלות רישום של טריגר, ה-URI של נקודת הקצה הזו הוא:

https://adtech.example/.well-known/attribution-reporting/report-aggregate-attribution

גם השדות המוצפנים וגם השדות הלא מוצפנים מאוכלסים בדוחות שניתן לצבור. הדוחות המוצפנים מאפשרים לכם להתחיל לבצע בדיקות באמצעות שירות הצבירה, והשדה הלא מוצפן מספק תובנות לגבי האופן שבו זוגות של ערכי מפתח שהוגדרו יוצרים את מבנה הנתונים.

מגדירים את השרת כך שיקבל בקשות JSON בפורמט הבא:

{
  // Info that the aggregation services also need encoded in JSON
  // for use with AEAD. Line breaks added for readability.
  "shared_info": "{
     \"api\":\"attribution-reporting\",
     \"attribution_destination\": \"android-app://com.advertiser.example.advertiser\",
     \"scheduled_report_time\":\"[timestamp in seconds]\",
     \"source_registration_time\": \"[timestamp in seconds]\",
     \"version\":\"[api version]\",
     \"report_id\":\"[UUID]\",
     \"reporting_origin\":\"https://reporter.example\" }",

  // In the current Developer Preview release, The "payload" and "key_id" fields
  // are not used because the platform doesn't yet encrypt aggregate reports.
  // The "debug_cleartext_payload" field holds unencrypted reports.
  "aggregation_service_payloads": [
    {
      "payload": "[base64 HPKE encrypted data readable only by the aggregation service]",
      "key_id": "[string identifying public key used to encrypt payload]",

      "debug_cleartext_payload": "[unencrypted payload]"
    },
  ],

  "source_debug_key": "[64 bit unsigned integer]",
  "trigger_debug_key": "[64 bit unsigned integer]"
}

מפתחות ניפוי באגים מאפשרים לקבל תובנות נוספות לגבי דוחות הייחוס. מידע נוסף על הגדרת מפתחות ניפוי באגים

הגדרה של לקוח Android

אפליקציית הלקוח רושמת מקורות שיוך וטריגרים, ומאפשרת יצירה של דוחות ברמת האירוע ודוחות מצטברים. כדי להכין מכשיר או אמולטור לקוח של Android לשימוש ב-Attribution Reporting API, מבצעים את הפעולות הבאות:

  1. מגדירים את סביבת הפיתוח לארגז החול לפרטיות ב-Android.
  2. מתקינים קובץ אימג' של המערכת במכשיר נתמך או מגדירים אמולטור שכולל תמיכה בארגז החול לפרטיות ב-Android.
  3. מפעילים גישה ל-Attribution Reporting API על ידי הפעלת פקודת ה-ADB הבאה. (ממשק ה-API מושבת כברירת מחדל).

    adb shell device_config put adservices ppapi_app_allow_list \"\*\"
  4. אם אתם בודקים באופן מקומי את Attribution Reporting API (למשל, בודקים במכשיר שיש לכם גישה פיזית אליו), מריצים את הפקודה הזו כדי להשבית את ההרשמה:

    adb shell device_config put adservices disable_measurement_enrollment_check "true"
  5. כדי להשתמש בממשקי Attribution Reporting API, צריך לכלול את ההרשאה ACCESS_ADSERVICES_ATTRIBUTION בקובץ מניפסט של Android וליצור הגדרה של שירותי פרסום לאפליקציה:

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_ATTRIBUTION" />
    
  6. (אופציונלי) אם אתם מתכננים לקבל דוחות ניפוי באגים, צריך לכלול את ההרשאה ACCESS_ADSERVICES_AD_ID בקובץ מניפסט של Android:

    <uses-permission android:name="android.permission.ACCESS_ADSERVICES_AD_ID" />
    
  7. מפנים להגדרות של שירותי פרסום ברכיב <application> של קובץ המניפסט:

    <property android:name="android.adservices.AD_SERVICES_CONFIG"
              android:resource="@xml/ad_services_config" />
    
  8. מציינים את משאב ה-XML של שירותי הפרסום שאליו יש הפניה במניפסט, כמו res/xml/ad_services_config.xml. מידע נוסף על הרשאות של שירותי פרסום ובקרת גישה ל-SDK

    <ad-services-config>
        <attribution allowAllToAccess="true" />
    </ad-services-config>
    

רישום אירועים שקשורים למודעות

האפליקציה צריכה לרשום מקורות והמרות כשהם מתרחשים כדי לוודא שהם מדווחים בצורה נכונה. במחלקת MeasurementManager יש שיטות שיעזרו לכם לרשום אירועים של מקורות שיוך (Attribution) וטריגרים להמרות.

רישום של אירוע מקור שיוך

כשמשתמש צופה במודעה או לוחץ עליה, אפליקציית בעל התוכן הדיגיטלי קוראת ל-registerSource() כדי לרשום מקור שיוך, כפי שמוצג בקטע הקוד.

‫Attribution Reporting API תומך בסוגים הבאים של אירועים של מקורות שיוך:

  • קליקים, שבדרך כלל נרשמים בשיטת קריאה חוזרת (callback) בדומה ל-onClick(). אירוע ההפעלה התואם מתרחש בדרך כלל זמן קצר אחרי אירוע מסוג קליק. סוג האירוע הזה מספק מידע נוסף על האינטראקציה של המשתמש, ולכן הוא מקור שיוך טוב שחשוב לתת לו עדיפות גבוהה.
  • צפיות, שבדרך כלל נרשמות בשיטת קריאה חוזרת (callback) שדומה ל-onAdShown(). אירוע ההפעלה המתאים עשוי להתרחש שעות או ימים אחרי אירוע הצפייה.

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)
var exampleClickEvent: InputEvent? = null

// Use the URI of the server-side endpoint that accepts attribution source
// registration.
val attributionSourceUri: Uri =
  Uri.parse("https://adtech.example/attribution_source?AD_TECH_PROVIDED_METADATA")

val future = CompletableFuture<Void>()

adView.setOnTouchListener(_: View?, event: MotionEvent?)) ->
    exampleClickEvent = event
    true
}

// Register Click Event
measurementManager.registerSource(
        attributionSourceUri,
        exampleClickEvent,
        CALLBACK_EXECUTOR,
        future::complete)

// Register View Event
measurementManager.registerSource(
        attributionSourceUri,
        null,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
private InputEvent exampleClickEvent;

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URI of the server-side endpoint that accepts attribution source
// registration.
Uri attributionSourceUri =
Uri.parse("https://adtech.example/attribution_source?AD_TECH_PROVIDED_METADATA");

CompletableFuture<Void> future = new CompletableFuture<>();

adView.setOnTouchListener(v, event)) -> {
    exampleClickEvent = event;
    return true;
}

// Register Click Event
measurementManager.registerSource(attributionSourceUri, exampleClickEvent,
        CALLBACK_EXECUTOR, future::complete);

// Register View Event
measurementManager.registerSource(attributionSourceUri, null,
        CALLBACK_EXECUTOR, future::complete);

אחרי הרישום, ה-API שולח בקשת HTTP POST לנקודת הקצה של השירות בכתובת שצוינה על ידי attributionSourceUri. התגובה של נקודת הקצה כוללת ערכים של destination, source_event_id, expiry ושל source_priority.

אם ספק הפרסום הדיגיטלי המקורי רוצה לשתף רישומים של מקורות, ה-URI המקורי של מקור השיוך יכול לכלול הפניות אוטומטיות לנקודות קצה אחרות של פרסום דיגיטלי. בהצעה הטכנית מפורטות המגבלות והכללים שחלים על ההפניות האוטומטיות.

הוספנו תמיכה בהפניות בשרשרת עבור registerSource ו-registerTrigger. בנוסף לכותרת הרישום, צרכן ה-API יכול עכשיו לספק הפניה אוטומטית של HTTP כתגובת השרת, שכוללת קוד סטטוס 302 וכותרת Location עם כתובת ה-URL הבאה שאליה צריך לעבור לרישום נוסף.

רק השדה 'יעד' שמופיע בביקור הראשון משמש בכל שרשרת ההפניות. מספר הביקורים מוגבל כמו מספר הכותרות של 'הפניה לדוח שיוך'. התמיכה בהפניות האלה היא בנוסף לתמיכה הקיימת ב-Attribution-Reporting-Redirect. אם שני סוגי ההפניות קיימים, המערכת תעדיף את Attribution-Reporting-Redirect.

רישום של אירוע מפעיל המרה

כדי לרשום אירוע הפעלה של המרה, קוראים לפונקציה registerTrigger() באפליקציה:

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)

// Use the URI of the server-side endpoint that accepts trigger registration.
val attributionTriggerUri: Uri =
    Uri.parse("https://adtech.example/trigger?AD_TECH_PROVIDED_METADATA")

val future = CompletableFuture<Void>()

// Register trigger (conversion)
measurementManager.registerTrigger(
        attributionTriggerUri,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URI of the server-side endpoint that accepts trigger registration.
Uri attributionTriggerUri =
        Uri.parse("https://adtech.example/trigger?AD_TECH_PROVIDED_METADATA");

CompletableFuture<Void> future = new CompletableFuture<>();

// Register trigger (conversion)
measurementManager.registerTrigger(
        attributionTriggerUri,
        CALLBACK_EXECUTOR,
        future::complete)

אחרי הרישום, ה-API שולח בקשת HTTP POST לנקודת הקצה של השירות בכתובת שצוינה על ידי attributionTriggerUri. התגובה של נקודת הקצה כוללת ערכים של אירועים ודוחות מצטברים.

אם פלטפורמת הפרסום הדיגיטלי המקורית מאפשרת שיתוף של רישום טריגרים, ה-URI יכול לכלול הפניות אוטומטיות לכתובות URI ששייכות לפלטפורמות אחרות של פרסום דיגיטלי. המגבלות והכללים שחלים על ההפניות האוטומטיות מפורטים בהצעה הטכנית.

רישום של מדידה באפליקציות ובאתרים שונים

אם גם אפליקציה וגם דפדפן ממלאים תפקיד במסלול של המשתמש מהמקור להפעלת האירוע, יש הבדלים קלים בהטמעה של רישום אירועים שקשורים למודעות. אם משתמש רואה מודעה באפליקציה ומופנה לדפדפן כדי להשלים המרה, המקור נרשם על ידי האפליקציה וההמרה נרשמת על ידי דפדפן האינטרנט. באופן דומה, אם משתמש מתחיל בדפדפן אינטרנט ומופנה לאפליקציה להשלמת ההמרה, הדפדפן רושם את המקור והאפליקציה רושמת את ההמרה.

יש הבדלים באופן שבו טכנולוגיות פרסום מאורגנות באינטרנט וב-Android, ולכן הוספנו ממשקי API חדשים לרישום מקורות וטריגרים כשהם מתרחשים בדפדפנים. ההבדל העיקרי בין ממשקי ה-API האלה לבין ממשקי ה-API המקבילים שמבוססים על אפליקציות הוא שאנחנו מצפים שהדפדפן יעקוב אחרי ההפניות האוטומטיות, יחיל מסננים ספציפיים לדפדפן ויעביר את הרישומים התקינים לפלטפורמה באמצעות קריאה ל-registerWebSource() או ל-registerWebTrigger().

בקטע הקוד הבא מוצגת דוגמה לקריאה ל-API שהדפדפן מבצע כדי לרשום מקור שיוך לפני שהוא מפנה את המשתמש לאפליקציה:

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager =
        context.getSystemService(MeasurementManager::class.java)
var exampleClickEvent: InputEvent? = null

// Use the URIs of the server-side endpoints that accept attribution source
// registration.
val sourceParam1 = WebSourceParams.Builder(Uri.parse(
        "https://adtech1.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
// True, if debugging is allowed for the ad tech.
    .setDebugKeyAllowed(true)
    .build()

val sourceParam2 = WebSourceParams.Builder(Uri.parse(
        "https://adtech2.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build()

val sourceParam3 = WebSourceParams.Builder(Uri.parse(
        "https://adtech3.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .build()

val sourceParams = Arrays.asList(sourceParam1, sourceParam2, sourceParam3)
val publisherOrigin = Uri.parse("https://publisher.example")
val appDestination = Uri.parse("android-app://com.example.store")
val webDestination = Uri.parse("https://example.com")

val future = CompletableFuture<Void>()

adView.setOnTouchListener {_: View?, event: MotionEvent? ->
    exampleClickEvent = event
    true
}
val clickRegistrationRequest = WebSourceRegistrationRequest.Builder(
          sourceParams,
          publisherOrigin)
      .setAppDestination(appDestination)
      .setWebDestination(webDestination)
      .setInputEvent(event)
      .build()
val viewRegistrationRequest = WebSourceRegistrationRequest.Builder(
          sourceParams,
          publisherOrigin)
      .setAppDestination(appDestination)
      .setWebDestination(webDestination)
      .setInputEvent(null)
      .build()

// Register a web source for a click event.
measurementManager.registerWebSource(
        clickRegistrationRequest,
        CALLBACK_EXECUTOR,
        future::complete)

// Register a web source for a view event.
measurementManager.registerWebSource(
        viewRegistrationRequest,
        CALLBACK_EXECUTOR,
        future::complete)

Java

private static final Executor CALLBACK_EXECUTOR =
        Executors.newCachedThreadPool();
private InputEvent exampleClickEvent;

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URIs of the server-side endpoints that accept attribution source
// registration.
WebSourceParams sourceParam1 = WebSourceParams.Builder(Uri.parse(
        "https://adtech1.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the ad tech.
    .setDebugKeyAllowed(true)
    .build();

WebSourceParams sourceParam2 = WebSourceParams.Builder(Uri.parse(
        "https://adtech2.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build();

WebSourceParams sourceParam3 = WebSourceParams.Builder(Uri.parse(
        "https://adtech3.example/attribution_source?AD_TECH_PROVIDED_METADATA"))
    .build();

List<WebSourceParams> sourceParams =
        Arrays.asList(sourceParam1, sourceParam2, sourceParam3);
Uri publisherOrigin = Uri.parse("https://publisher.example");
Uri appDestination = Uri.parse("android-app://com.example.store");
Uri webDestination = Uri.parse("https://example.com");

CompletableFuture<Void> future = new CompletableFuture<>();

adView.setOnTouchListener(v, event) -> {
    exampleClickEvent = event;
    return true;
}

WebSourceRegistrationRequest clickRegistrationRequest =
        new WebSourceRegistrationRequest.Builder(sourceParams, publisherOrigin)
    .setAppDestination(appDestination)
    .setWebDestination(webDestination)
    .setInputEvent(event)
    .build();
WebSourceRegistrationRequest viewRegistrationRequest =
        new WebSourceRegistrationRequest.Builder(sourceParams, publisherOrigin)
    .setAppDestination(appDestination)
    .setWebDestination(webDestination)
    .setInputEvent(null)
    .build();

// Register a web source for a click event.
measurementManager.registerWebSource(clickRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

// Register a web source for a view event.
measurementManager.registerWebSource(viewRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

בקטע הקוד הבא מוצגת דוגמה לקריאה ל-API שהדפדפן מבצע כדי לרשום המרה אחרי שהמשתמש מופנה מהאפליקציה:

Kotlin

companion object {
    private val CALLBACK_EXECUTOR = Executors.newCachedThreadPool()
}

val measurementManager = context.getSystemService(MeasurementManager::class.java)

// Use the URIs of the server-side endpoints that accept trigger registration.
val triggerParam1 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech1.example/trigger?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the ad tech.
    .setDebugKeyAllowed(true)
    .build()

val triggerParam2 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech2.example/trigger?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build()

val triggerParams = Arrays.asList(triggerParam1, triggerParam2)
val advertiserOrigin = Uri.parse("https://advertiser.example")

val future = CompletableFuture<Void>()

val triggerRegistrationRequest = WebTriggerRegistrationRequest.Builder(
        triggerParams,
        advertiserOrigin)
    .build()

// Register the web trigger (conversion).
measurementManager.registerWebTrigger(
    triggerRegistrationRequest,
    CALLBACK_EXECUTOR,
    future::complete)

Java

private static final Executor CALLBACK_EXECUTOR =
        Executors.newCachedThreadPool();

MeasurementManager measurementManager =
        context.getSystemService(MeasurementManager.class);

// Use the URIs of the server-side endpoints that accept trigger registration.
WebTriggerParams triggerParam1 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech1.example/trigger?AD_TECH_PROVIDED_METADATA"))
    // True, if debugging is allowed for the ad tech.
    .setDebugKeyAllowed(true)
    .build();

WebTriggerParams triggerParam2 = WebTriggerParams.Builder(Uri.parse(
        "https://adtech2.example/trigger?AD_TECH_PROVIDED_METADATA"))
    .setDebugKeyAllowed(false)
    .build();

List<WebTriggerParams> triggerParams =
        Arrays.asList(triggerParam1, triggerParam2);
Uri advertiserOrigin = Uri.parse("https://advertiser.example");

CompletableFuture<Void> future = new CompletableFuture<>();

WebTriggerRegistrationRequest triggerRegistrationRequest =
        new WebTriggerRegistrationRequest.Builder(
            triggerParams, advertiserOrigin)
    .build();

// Register the web trigger (conversion).
measurementManager.registerWebTrigger( triggerRegistrationRequest,
        CALLBACK_EXECUTOR, future::complete);

הוספת רעש להגנה על הפרטיות

דוחות ברמת האירוע מכילים נתוני יעד, מזהה מקור שיוך וטריגר. הן נשלחות בפורמט המקורי (לא מוצפן) למקור הדיווח. כדי להגן על פרטיות המשתמשים, אפשר להוסיף רעש כדי להקשות על זיהוי משתמשים פרטיים. דוחות ברמת האירוע עם נתונים מיותרים נוצרים ונשלחים בהתאם למסגרת הפרטיות הדיפרנציאלית. אלה ערכי ברירת המחדל של אחוז הרעש בתרחישים שונים:

סוג המקור

ערך יעד המקור

הסתברות משוערת של דוח לכל רישום מקור

הצגה

אפליקציה או אתר

0.0000025

הצגה

אפליקציה ואתר

0.0000042

קליק

אפליקציה או אתר

0.0024263

קליק

אפליקציה ואתר

0.0170218

במדידת שיוך (Attribution) של המרות מאפליקציה לאתר, שבה מקורות יכולים להניב המרות גם באפליקציה וגם באתר, בדוחות ברמת האירוע אפשר לציין אם ההפעלה התרחשה באפליקציה או באתר. כדי לפצות על הפרטים הנוספים האלה, הדוחות עם הרעש שנוצרים הם עד פי 7 בערך לקליקים ועד פי 1.7 בערך לצפיות.

חלק מהטכנולוגיות הפרסומיות לא דורשות דוחות ברמת האירוע כדי לציין אם הטריגר התרחש באפליקציה או ביעד האינטרנטי. טכנולוגיות פרסום יכולות להשתמש בשדה coarse_event_report_destinations מתחת לכותרת Attribution-Reporting-Register-Source כדי לצמצם את הרעש. אם מקור עם השדה coarse_event_report_destinations שצוין זוכה בשיוך, הדוח שמתקבל כולל גם יעדים באפליקציה וגם יעדים באינטרנט, בלי להבחין בין המקומות שבהם התרחש הטריגר בפועל.

בדוגמאות הבאות, משתמש לוחץ על מודעה, והמקור הזה נרשם ב-API. לאחר מכן המשתמש משלים המרה באפליקציה של המפרסם ובאתר של המפרסם. שתי ההמרות האלה נרשמות כטריגרים ומיוחסות לקליק הראשוני.

כותרת HTTP של רישום מקור שמבוסס על קליקים:

Attribution-Reporting-Register-Source: {
    "destination": "android-app://com.advertiser.example",
    "web_destination": "https://advertiser.com",
    "source_event_id": "234",
    "expiry": "60000",
    "priority": "5",
    // Ad tech opts out of receiving app-web destination distinction
    // in event report, avoids additional noise
    "coarse_event_report_destinations": "true"
}

הטריגר נרשם מהאפליקציה עם שם החבילה com.advertiser.example:

Attribution-Reporting-Register-Trigger: {
    "event_trigger_data": [{
    "trigger_data": "1",
    "priority": "1"
    }],
}

טריגר נרשם מדפדפן מהאתר עם הדומיין eTLD+1 https://advertiser.com:

Attribution-Reporting-Register-Trigger: {
    "event_trigger_data": [{
    "trigger_data": "2",
    "priority": "2"
    }],
}

המערכת תפיק דוחות ברמת האירוע. בהנחה ששני הטריגרים משויכים למקור, הדוחות הבאים ברמת האירוע נוצרים:

  {
    "attribution_destination": ["android-app://com.advertiser.example,https://advertiser.com"],
    "scheduled_report_time": "800176400",
    "source_event_id": "53234",
    "trigger_data": "1",
    // Can be "event" if source were registered by user viewing the ad
    "source_type": "navigation",
    // Would be 0.0170218 without coarse_event_report_destinations as true in the source
    "randomized_trigger_rate": 0.0024263
  }

יצירה ושליחה של דוחות

‫Attribution Reporting API שולח דוחות לנקודות הקצה בשרת שלכם שמקבלות דוחות ברמת האירוע ודוחות נצברים.

אילוץ הרצה של משימות דיווח

אחרי שרושמים אירוע של מקור שיוך או אירוע של טריגר, המערכת מתזמנת את הרצת משימת הדיווח. כברירת מחדל, העבודה הזו מופעלת כל 4 שעות. לצורך בדיקה, אפשר להריץ את משימות הדיווח או לקצר את המרווחים בין המשימות.

אילוץ הפעלה של משימת שיוך:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 5

כדי להריץ בכוח משימת דיווח ברמת האירוע:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 3

הפעלת משימת דיווח שניתן לצבור:

adb shell cmd jobscheduler run -f com.google.android.adservices.api 7

בודקים את הפלט ב-Logcat כדי לראות מתי העבודות רצו. הוא אמור להיראות בערך כך:

JobScheduler: executeRunCommand(): com.google.android.adservices.api/0 5 s=false f=true

כפיית מסירה של דוחות

גם אם מריצים את עבודת הדיווח בכוח, המערכת עדיין שולחת את הדוחות בהתאם למועדי המסירה המתוזמנים שלהם, שנעים בין כמה שעות לכמה ימים. לצורך בדיקה, אפשר להקדים את השעה במערכת של המכשיר כך שתהיה אחרי העיכובים המתוזמנים כדי להתחיל את מסירת הדוח.

אימות הדוחות בשרת

אחרי שליחת הדוחות, בודקים את המסירה שלהם באמצעות בדיקת הדוחות שהתקבלו, יומני השרת הרלוונטיים כמו היסטוריית השרת המדומה או המערכת המותאמת אישית שלכם.

פענוח הדוח המצטבר

כשמקבלים דוח מצטבר, השדה debug_cleartext_payload מכיל גרסה לא מוצפנת של הדוח המצטבר. הגרסה הזו של הדוח לא מוצפנת, אבל עדיין צריך לפענח אותה.

הדוגמה הבאה ממחישה איך לפענח את התוכן של השדה debug_cleartext_payload בשני שלבים: הראשון באמצעות פענוח Base 64, והשני באמצעות פענוח CBOR.

String base64DebugPayload  = "omRkYXRhgqJldmFsdWVEAAAGgGZidWNrZXRQAAAAAAAAAAAAAAAAAAAKhaJldmFsdWVEAACAAGZidWNrZXRQAAAAAAAAAAAAAAAAAAAFWWlvcGVyYXRpb25paGlzdG9ncmFt";
byte[] cborEncoded = Base64.getDecoder().decode(base64DebugPayload);

// CbodDecoder comes from this library https://github.com/c-rack/cbor-java
final List<DataItem> dataItems = new CborDecoder(new ByteArrayInputStream(cborEncoded)).decode();

// In here you can see the contents, but the value will be something like:
// Data items: [{ data: [{ value: co.nstant.in.cbor.model.ByteString@a8b5c07a,
//   bucket: co.nstant.in.cbor.model.ByteString@f812097d },
//   { value: co.nstant.in.cbor.model.ByteString@a8b5dfc0,
//   bucket: co.nstant.in.cbor.model.ByteString@f8120934 }], operation: histogram }]
Log.d("Data items : " + dataItems);

// In order to see the value for bucket and value, you can traverse the data
// and get their values, something like this:
final Map payload = (Map) dataItems.get(0);
final Array payloadArray = (Array) payload.get(new UnicodeString("data"));

payloadArray.getDataItems().forEach(i -> {
    BigInteger value = new BigInteger(((ByteString) ((Map)i).get(new UnicodeString("value"))).getBytes());
    BigInteger bucket = new BigInteger(((ByteString) ((Map)i).get(new UnicodeString("bucket"))).getBytes());
    Log.d("value : " + value + " ;bucket : " + bucket);
});

בדיקה

כדי לעזור לכם להתחיל להשתמש ב-Attribution Reporting API, אתם יכולים להשתמש בפרויקט MeasurementSampleApp ב-GitHub. באפליקציה לדוגמה הזו מוצגות דוגמאות לרישום של מקורות שיוך ולרישום של טריגרים.

במקרה של נקודות קצה של שרתים, כדאי לעיין במקורות המידע הבאים או בפתרון המותאם אישית שלכם:

  • MeasurementAdTechServerSpec כולל הגדרות שירות של OpenAPI, שאפשר לפרוס בפלטפורמות נתמכות של מיקרו-שירותים או של שירותים מדומים.
  • MeasurementAdTechServer כולל יישום לדוגמה של שרת מדומה שמבוסס על אפליקציית Spring Boot ל-Google App Engine.

דרישות מוקדמות

פריסת ממשקי API מדומים בנקודות קצה מרוחקות שאפשר לגשת אליהן ממכשיר הבדיקה או מהאמולטור. כדי להקל על הבדיקה, אפשר לעיין בפרויקטים לדוגמה MeasurementAdTechServerSpec ו-MeasurementAdTechServer.

פונקציונליות לבדיקה

תכונות שיושקו בקרוב

הגדרה גמישה ברמת האירוע

מומלץ להשתמש בהגדרת ברירת המחדל לדיווח ברמת האירוע כדי להתחיל לבדוק את התועלת, אבל יכול להיות שהיא לא תתאים לכל תרחישי השימוש. ‫Attribution Reporting API יתמוך בהגדרות אופציונליות וגמישות יותר, כדי שחברות AdTech יוכלו לשלוט יותר במבנה של הדוחות ברמת האירוע ולמקסם את התועלת של הנתונים. הגמישות הנוספת הזו תוטמע ב-Attribution Reporting API בשני שלבים:

  • שלב 1: הגדרה גמישה של אירועים ברמת Lite; קבוצת משנה של שלב 2.
  • שלב 2: גרסה מלאה של הגדרה גמישה ברמת האירוע.

שלב 1: הגדרות גמישות ברמת האירוע בגרסת Lite

נוסיף את שני הפרמטרים האופציונליים הבאים ל-JSON ב-Attribution-Reporting-Register-Source:

  • max_event_level_reports
  • event_report_windows
{
  ...
  // Optional. This is a parameter that acts across all trigger types for the
  // lifetime of this source. It restricts the total number of event-level
  // reports that this source can generate. After this maximum is hit, the
  // source is no longer capable of producing any new data. The use of
  // priority in the trigger attribution algorithm in the case of multiple
  // attributable triggers remains unchanged. Defaults to 3 for navigation
  // sources and 1 for event sources
  "max_event_level_reports": <int>,

  // Optional. Represents a series of time windows, starting at 0. Reports
  // for this source will be delivered an hour after the end of each window.
  // Time is encoded as seconds after source registration. If
  // event_report_windows is omitted, will use the default windows. This
  // field is mutually exclusive with the existing `event_report_window` field.
  // // End time is exclusive.
  "event_report_windows": {
    "start_time": <int>,
    "end_times": [<int>, ...]
  }
}

דוגמה להגדרה בהתאמה אישית

הגדרת הדוגמה הזו תומכת במפתח שרוצה לבצע אופטימיזציה כדי לקבל דוחות בחלונות דיווח מוקדמים יותר.

{
  ...
  "max_event_level_reports": 2,
  "event_report_windows": {
    "end_times": [7200, 43200, 86400] // 2 hours, 12 hours, 1 day in seconds
  }
}

שלב 2: גמישות מלאה ברמת האירוע

בנוסף לפרמטרים שהוספנו בשלב 1, נוסיף פרמטר אופציונלי נוסף trigger_specs ל-JSON ב-Attribution-Reporting-Register-Source.

{
  // A trigger spec is a set of matching criteria, along with a scheme to
  // generate bucketized output based on accumulated values across multiple
  // triggers within the specified event_report_window. There will be a limit on
  // the number of specs possible to define for a source.
  "trigger_specs": [{
    // This spec will only apply to registrations that set one of the given
    // trigger data values (non-negative integers) in the list.
    // trigger_data will still appear in the event-level report.
    "trigger_data": [<int>, ...]

    // Represents a series of time windows, starting at the source registration
    // time. Reports for this spec will be delivered an hour after the end of
    // each window. Time is encoded as seconds after source registration.
    // end_times must consist of strictly increasing positive integers.
    //
    // Note: specs with identical trigger_data cannot have overlapping windows;
    // this makes sure that triggers match at most one spec. If
    // event_report_windows is omitted, will use the "event_report_window" or
    // "event_report_windows" field specified at the global level for the source
    // (or the default windows if none are specified). End time is exclusive.
    "event_report_windows": {
      "start_time": <int>,
      "end_times": [<int>, ...],
    }

    // Represents an operator that summarizes the triggers within a window
    // count: number of triggers attributed within a window
    // value_sum: sum of the value of triggers within a window
    // The summary is reported as an index into a bucketization scheme. Defaults
    // to "count"
    "summary_window_operator": <one of "count" or "value_sum">,

    // Represents a bucketization of the integers from [0, MAX_INT], encoded as
    // a list of integers where new buckets begin (excluding 0 which is
    // implicitly included).
    // It must consist of strictly increasing positive integers.
    //
    // e.g. [5, 10, 100] encodes the following ranges:
    // [[0, 4], [5, 9], [10, 99], [100, MAX_INT]]
    //
    // At the end of each reporting window, triggers will be summarized into an
    // integer which slots into one of these ranges. Reports will be sent for
    // every new range boundary that is crossed. Reports will never be sent for
    // the range that includes 0, as every source is initialized in this range.
    //
    // If omitted, then represents a trivial mapping
    // [1, 2, ... , MAX_INT]
    // With MAX_INT being the maximum int value defined by the browser.
    "summary_buckets": [<bucket start>, ...]
  }, {
    // Next trigger_spec
  } ...],

  // See description in phase 1.
  "max_event_level_reports": <int>
  // See description in phase 1.
  "event_report_windows": {
    "start_time": <int>,
    "end_times": [<int>, ...]
  }
}

ההגדרה הזו מציינת באופן מלא את מרחב הפלט של הדוחות ברמת האירוע, לכל רישום של מקור. לכל מפרט של טריגר, אנחנו מציינים באופן מלא את הפרטים הבאים:

  • קבוצה של קריטריונים להתאמה:
    • אילו נתונים ספציפיים של טריגר המפרט הזה חל עליהם. המקור הזה עומד בדרישות להתאמה רק להפעלות שיש להן אחד מהערכים שצוינו trigger_data ב-trigger_specs. במילים אחרות, אם הטריגר היה תואם למקור הזה אבל ה-trigger_data שלו לא היה אחד מהערכים בהגדרות של המקור, המערכת תתעלם מהטריגר.
    • כשמפעיל ספציפי תואם למפרט הזה (באמצעות event_report_windows). שימו לב: יכול להיות שהמפעיל עדיין יתאים למקור של דוחות מצטברים, למרות שהוא לא עומד בשני קריטריוני ההתאמה שצוינו קודם.
  • אלגוריתם ספציפי לסיכום ולסיווג של כל הטריגרים בחלון השיוך. כך אפשר להגדיר בטריגרים פרמטר value שסוכם עבור מפרט מסוים, אבל מדווח כערך בקבוצה.

הטריגרים יתמכו גם בהוספה של פרמטר ערך אופציונלי במילונים בתוך event_trigger_data.

{
  "event_trigger_data": [
    {
      "trigger_data": "2",
      "value": 100,  // Defaults to 1
      "filters": ...
    },
    ...
  ]
}

כל רישום של טריגר יתאים לכל היותר למפרט טריגר אחד ויעדכן את ערך הסיכום המשויך. ברמת העל, בזמן ההפעלה:

  • החלת מסנני שיוך גלובליים.
  • לכל מפרט של טריגר, המערכת מעריכה את event_trigger_data במפרט כדי למצוא התאמה, באמצעות event_reporting_window של המפרט. השדה event_reporting_windows ברמה העליונה משמש כערך ברירת מחדל למקרה ששדה המשנה event_report_windows חסר במפרט של טריגר כלשהו.
  • המפרט הראשון התואם נבחר לשיוך, וערך הסיכום גדל ב-value.

כשמפרט event_report_window מסתיים, אנחנו ממפים את ערך הסיכום שלו למאגר, ושולחים דוח ברמת האירוע על כל עלייה במאגר הסיכום שנגרמת על ידי ערכי טריגר משויכים. הדוחות יכללו שדה נוסף אחד, trigger_summary_bucket.

{
  ...
  "trigger_summary_bucket": [<bucket start>, <bucket end>],
}

הגדרות ששקולות לגרסה הנוכחית

ההגדרות הבאות שקולות להגדרות של מקורות האירועים והניווט הנוכחיים של ממשקי ה-API, בהתאמה. במיוחד כשמדובר במקורות ניווט, אפשר לראות למה רמות הרעש כל כך גבוהות יחסית למקורות אירועים כדי לשמור על אותם ערכי אפסילון: למקורות ניווט יש מרחב פלט גדול בהרבה.

יכול להיות שיש כמה הגדרות ששקולות זו לזו, בהתחשב בכך שאפשר להגדיר חלק מהפרמטרים כברירת מחדל או להסיר אותם.

מקורות אירועים מקבילים
// Note: most of the fields here are not required to be explicitly listed.
// Here we list them explicitly just for clarity.
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1],
    "event_report_windows": {
      "end_times": [<30 days>]
    },
    "summary_window_operator": "count",
    "summary_buckets": [1],
  }],
  "max_event_level_reports": 1,
  ...
  // expiry must be greater than or equal to the last element of the end_times
  "expiry": <30 days>,
}
מקורות ניווט מקבילים
// Note: most of the fields here are not required to be explicitly listed.
// Here we list them explicitly just for clarity.
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1, 2, 3, 4, 5, 6, 7],
    "event_report_windows": {
      "end_times": [<2 days>, <7 days>, <30 days>]
    },
    "summary_window_operator": "count",
    "summary_buckets": [1, 2, 3],
  }],
  "max_event_level_reports": 3,
  ...
  // expiry must be greater than or equal to the last element of the end_times
  "expiry": <30 days>,
}

דוגמאות להגדרות מותאמות אישית

בהמשך מפורטות כמה הגדרות נוספות שחורגות מהגדרות ברירת המחדל. בכל הדוגמאות האלה, הפשרות שהמפתח צריך לעשות כוללות:

  • צמצום של מאפיין מסוים בהגדרת ברירת המחדל (מספר הטריגרים, עוצמת הנתונים של הטריגר, מספר החלונות) כדי להגדיל מאפיין אחר ולשמור על רמת הרעש
  • הפחתת חלק מהמאפיינים של הגדרת ברירת המחדל (מספר הטריגרים, הקרדינליות של נתוני הטריגר, מספר החלונות) כדי להפחית את רמת הרעש

דיווח על קבוצות של ערכי טריגר

הגדרת הדוגמה הזו תומכת במפתח שרוצה לבצע אופטימיזציה לנתוני ערך רק בחלון דיווח אחד (לדוגמה, 7 ימים), ומעדיף פחות חלונות דיווח כדי להפחית את הרעש. בדוגמה הזו, כל טריגר שמגדיר את trigger_data לערך שאינו 0 לא עומד בדרישות לשיוך.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      "end_times": [604800, 1209600] // 7 days, 14 days represented in seconds
    },
    "summary_window_operator": "value_sum",
    "summary_buckets": [5, 10, 100]
  }],
}

אפשר לרשום טריגרים עם השדה value, שערכיו מסוכמים ומסווגים. לדוגמה, אם יש שלושה טריגרים תוך 7 ימים ממועד הרישום של מקורות עם הערכים 1, 3 ו-4.

{ "event_trigger_data": [{"trigger_data": "0", "value": 1}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 3}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 4}] }

הערכים מסוכמים ל-8 ומדווחים בדוחות הבאים אחרי 7 ימים + שעה אחת:

// Report 1
{
  ...
  "trigger_summary_bucket": [5, 9]
}

במהלך 7 הימים הבאים, הטריגרים הבאים נרשמים:

{ "event_trigger_data": [{"trigger_data": "0", "value": 50}] }
{ "event_trigger_data": [{"trigger_data": "0", "value": 45}] }

הערכים מסוכמים ל-8 + 50 + 45 = 103. כתוצאה מכך, הדוחות הבאים נוצרים אחרי 14 ימים + שעה:

// Report 2
{
  ...
  "trigger_summary_bucket": [10, 99]
},

// Report 3
{
  ...
  "trigger_summary_bucket": [100, MAX_INT]
}
דיווח על מספר ההפעלות של הטריגרים

בדוגמה הזו מוצג איך מפתח יכול להגדיר מקור כדי לקבל ספירה של עד 10 טריגרים.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      "end_times": [604800] // 7 days represented in seconds
    },
    // This field could be omitted to save bandwidth since the default is "count"
    "summary_window_operator": "count",
    "summary_buckets": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  }],
}

טריגרים עם ייחוס למקורות תנועה שערך המאפיין trigger_data שלהם הוא 0 נספרים עד 10. המערכת מתעלמת מערך הטריגר כי summary_window_operator מוגדר לספירה. אם נרשמו 4 טריגרים שמשויכים למקור, הדוח ייראה כך:

// Report 1
{
  ...
  "trigger_summary_bucket": [1, 1]
}
// Report 2
{
  ...
  "trigger_summary_bucket": [2, 2]
}
// Report 3
{
  ...
  "trigger_summary_bucket": [3, 3]
}
// Report 4
{
  ...
  "trigger_summary_bucket": [4, 4]
}
בינארי עם דיווח תכוף יותר

ההגדרה הזו תומכת במפתח שרוצה לדעת אם התרחשה לפחות המרה אחת ב-10 הימים הראשונים (ללא קשר לערך), אבל רוצה לקבל דוחות במרווחי זמן קצרים יותר מאלה שמוגדרים כברירת מחדל. שוב, בדוגמה הזו, כל טריגר שמגדיר את trigger_data לערך שאינו 0 לא עומד בדרישות לשיוך. זו הסיבה לכך שתרחיש השימוש הזה נקרא בינארי.

{
  "trigger_specs": [
  {
    "trigger_data": [0],
    "event_report_windows": {
      // 1 day, 2 days, 3 days, 5 days, 7 days, 10 days represented in seconds
      "end_times": [86400, 172800, 259200, 432000, 604800, 864000]
    },
    // This field could be omitted to save bandwidth since the default is "count"
    "summary_window_operator": "count",
    "summary_buckets": [1]
  }],
}
שינוי מפרטי ההפעלה ממקור למקור
{
  "trigger_specs": [
  {
    "trigger_data": [0, 1, 2, 3],
    "event_report_windows": {
      "end_times": [172800, 604800, 2592000] // 2 days, 7 days, 30 days represented in seconds
    }
  }],
  "max_event_level_reports": 3
}
{
  "trigger_specs": [
  {
    "trigger_data": [4, 5, 6, 7],
    "event_report_windows": {
      "end_times": [172800, 604800, 2592000] // 2 days, 7 days, 30 days represented in seconds
    }
  }],
  "max_event_level_reports": 3
}

אנחנו מעודדים מפתחים להציע תרחישי שימוש שונים שיכולים להיות להם עם תוסף ה-API הזה, ואנחנו נעדכן את ההסבר הזה עם הגדרות לדוגמה לתרחישי השימוש האלה.

שיוך (Attribution) חוצה-פלטפורמות ללא הפניות אוטומטיות

טכנולוגיות פרסום צריכות להשתמש בהפניות אוטומטיות כדי לרשום כמה טריגרים של מקורות שיוך ולבצע שיוך חוצה-רשתות. התכונה הזו עוזרת לתמוך בשיוך (Attribution) בין רשתות, במקרים שבהם אי אפשר להשתמש בהפניות אוטומטיות בין רשתות. מידע נוסף

טכנולוגיות פרסום יכולות לשלוח הגדרה בתגובה לרישום הטריגר, על סמך המקורות שנרשמו על ידי טכנולוגיות פרסום אחרות ונבחרו ליצירת מקורות נגזרים. לאחר מכן, המקורות הנגזרים האלה משמשים לשיוך. דוחות צבירה נוצרים אם הטריגר משויך למקור נגזר. אין תמיכה ביצירת דוחות אירועים למקורות נתונים נגזרים.

ספקי טכנולוגיות פרסום יכולים לבחור מתוך aggregation_keys במקורות הרשומים שלהם את המקורות שהם רוצים לשתף עם ספקי טכנולוגיות פרסום שותפים. אפשר להצהיר על המפתחות האלה בשדה shared_aggregation_keys האופציונלי, שנמצא מתחת לכותרת של רישום המקור Attribution-Reporting-Register-Source:

"shared_aggregation_keys": ["[key name1]", "[key name2]"]

מקורות נגזרים נוצרים על סמך ההגדרה בכותרת של רישום הטריגר Attribution-Reporting-Register-Trigger:

  // Specifies the configuration based on which derived sources should be
  // generated. Those derived sources will be included for source matching at the
  // time of attribution. For example, if adtech2 is registering a trigger with an
  // attribution_config with source_network as adtech1, available sources
  // registered by adtech1 will be considered with additional filtering criteria
  // applied to that set as mentioned in the attribution_config. Derived
  // sources can have different values to priority, post_install_exclusivity_window
  // etc.

  "attribution_config": [
    {
      // Derived sources are created from this adtech's registered sources
      "source_network": "[original source's adtech enrollment ID]",
      //(optional) Filter sources whose priority falls in this range
      "source_priority_range": {
        "start": [priority filter lower bound],
        "end": [priority filter upper bound]
      },
      // (optional) Filter sources whose at least one of filter maps matches these
      // filters
      "source_filters": {
        "key name 1": ["key1 value 1"]
      },
      // (optional) Filter sources whose none of filter map matches these
      // filters
        "source_not_filters": {
          "key name 1": ["key1 value 1"]
        },
      // (optional) Apply this priority to the generated derived sources
      "priority": "[64 bit signed integer]",
      // (optional) The derived source will have expiry set as this or parent
      // source's, whichever is earlier
      "expiry": "[64 bit signed integer]",
      // (optional) set on the derived source
      "filter_data": {
        "key name 1": ["key1 value 1"]
      },
      // (optional) set on the derived source
      "post_install_exclusivity_window": "[64-bit unsigned integer]"
    }
  ]

זוהי גרסה עם ערכים לדוגמה:

  "attribution_config": [
    {
      "source_network": "adtech1-enrollment-id",
      "source_priority_range": {
        "start": 50,
        "end": 100
      },
      "source_filters": {
        "source_type": ["NAVIGATION"]
      },
      "source_not_filters": {
        "product_id": ["789"]
      },
      "priority": "30",
      "expiry": "78901",
      // (optional) set on the derived source
      "filter_data": {
        "product_id": ["1234"]
        },
      // (optional) set on the derived source
      "post_install_exclusivity_window": "7890"
    }
  ]

נוספו שני שדות אופציונליים חדשים לכותרת של טריגר ההרשמה. השדות האלה מאפשרים לזהות את הטכנולוגיה המנצחת של המודעות במפתחות של דוחות שניתן לצבור:

  • x_network_bit_mapping: מיפוי ביטים של מזהה ההצטרפות למזהה פרסום דיגיטלי
  • x_network_data: היסט (הזזה שמאלה) של הפעולה x_network_bit_mappingOR עם חלק המפתח של הטריגר בטכנולוגיית המודעות המנצחת
דוגמה:
"Attribution-Reporting-Register-Trigger": {
  "attribution_config": [...],
  "aggregatable_trigger_data": [
    {
     "key_piece": "0x400",
     "source_keys": ["campaignCounts"]
      "x_network_data" : {
        "key_offset" : 12 // [64 bit unsigned integer]
      }
    }
    
  ]
  
  "x_network_bit_mapping": {
   // This mapping is used to generate trigger key pieces with ad tech identifier
   // bits. eg. If Ad Tech-A's sources wins the attribution then 0x1 here will be
   // OR'd with the trigger key pieces to generate the final key piece.
    "Ad-Tech-A-enrollment_id": "0x1", // Identifier bits in hex for A
    "Ad-Tech-B-enrollment_id": "0x2"  // Identifier bits in hex for B
  }
  
}

חישוב המפתח של הטריגר שמתקבל כשיוצרים דוח עבור המקור של AdTechB:

  • key_piece: 0x400 (010000000000)
  • key_offset: 12
  • הערך של enrollment_id בטכנולוגיית הפרסום ב': 2 (010) (מ-x_network_bit_mapping)
  • החלק של מפתח הטריגר שנוצר: 0x400 | 0x2 << 12 = 0x2400

מגבלות

רשימת היכולות שנמצאות בתהליך פיתוח ב-זמן ריצה ל-SDK מופיעה בנתוני הגרסה.

דיווח על באגים ובעיות

המשוב שלכם חשוב מאוד לשיפור ארגז החול לפרטיות ב-Android. נשמח לדעת אם נתקלתם בבעיות או אם יש לכם רעיונות לשיפור ארגז החול לפרטיות ב-Android.