-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathiOSPrivacyManifestPostProcessor.cs
More file actions
133 lines (118 loc) · 5.38 KB
/
Copy pathiOSPrivacyManifestPostProcessor.cs
File metadata and controls
133 lines (118 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#nullable enable
using System.IO;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
#endif
namespace Immutable.Audience.Editor
{
/// <summary>
/// Patches <c>UnityFramework/PrivacyInfo.xcprivacy</c> to add IDFA tracking
/// declarations when <c>AUDIENCE_MOBILE_ATTRIBUTION</c> is enabled.
/// Unity auto-merges the default manifest; this post-processor adds
/// <c>NSPrivacyTracking=true</c> and <c>NSPrivacyCollectedDataTypeAdvertisingData</c>
/// in-place so Unity's own Required Reason API entries are preserved.
/// Runs at callbackOrder 9052, after the Info.plist (9050) and framework (9051) post-processors.
/// </summary>
internal static class iOSPrivacyManifestPostProcessor
{
internal const int CallbackOrder = 9052;
private const string BuiltManifestName = "PrivacyInfo.xcprivacy";
[PostProcessBuild(CallbackOrder)]
internal static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
{
if (target != BuildTarget.iOS) return;
#if UNITY_IOS
if (!AttributionDefineEnabled()) return;
var builtManifestPath = FindBuiltManifest(pathToBuiltProject);
if (builtManifestPath == null)
{
Debug.LogWarning(
$"[ImmutableAudience] iOS privacy manifest post-processor: {BuiltManifestName} not found " +
$"under {pathToBuiltProject}. Skipping attribution manifest update.");
return;
}
var plist = new PlistDocument();
plist.ReadFromFile(builtManifestPath);
ApplyAttributionPrivacyEntries(plist.root);
plist.WriteToFile(builtManifestPath);
#endif
}
private static string? FindBuiltManifest(string pathToBuiltProject)
{
// Unity 2019.3+ places the merged manifest here.
var candidate = Path.Combine(pathToBuiltProject, "UnityFramework", BuiltManifestName);
if (File.Exists(candidate)) return candidate;
// Fall back to a recursive search for older Unity layout variants.
var found = Directory.GetFiles(pathToBuiltProject, BuiltManifestName, SearchOption.AllDirectories);
return found.Length > 0 ? found[0] : null;
}
#if UNITY_IOS
/// <summary>
/// Adds the attribution-specific privacy declarations to an existing
/// (already Unity-merged) <c>PrivacyInfo.xcprivacy</c> plist root.
/// Idempotent. Safe to call on a manifest that already has these entries.
/// </summary>
internal static void ApplyAttributionPrivacyEntries(PlistElementDict root)
{
// IDFA collection constitutes tracking under Apple's definition.
root.SetBoolean("NSPrivacyTracking", true);
// Apple requires every domain contacted for tracking to be listed.
const string trackingDomain = "api.immutable.com";
PlistElementArray trackingDomains;
if (root.values.TryGetValue("NSPrivacyTrackingDomains", out var existingDomains) &&
existingDomains is PlistElementArray existingDomainsArray)
{
trackingDomains = existingDomainsArray;
}
else
{
trackingDomains = root.CreateArray("NSPrivacyTrackingDomains");
}
var hasDomain = false;
foreach (var item in trackingDomains.values)
{
if (item is PlistElementString ds && ds.value == trackingDomain) { hasDomain = true; break; }
}
if (!hasDomain) trackingDomains.AddString(trackingDomain);
PlistElementArray dataTypes;
if (root.values.TryGetValue("NSPrivacyCollectedDataTypes", out var existing) &&
existing is PlistElementArray existingArray)
{
dataTypes = existingArray;
}
else
{
dataTypes = root.CreateArray("NSPrivacyCollectedDataTypes");
}
// Avoid duplicate entries if the post-processor is re-run.
const string advertisingType = "NSPrivacyCollectedDataTypeAdvertisingData";
foreach (var item in dataTypes.values)
{
if (item is PlistElementDict d &&
d.values.TryGetValue("NSPrivacyCollectedDataType", out var v) &&
v is PlistElementString s &&
s.value == advertisingType)
return;
}
var entry = dataTypes.AddDict();
entry.SetString("NSPrivacyCollectedDataType", advertisingType);
entry.SetBoolean("NSPrivacyCollectedDataTypeLinked", true);
entry.SetBoolean("NSPrivacyCollectedDataTypeTracking", true);
var purposes = entry.CreateArray("NSPrivacyCollectedDataTypePurposes");
purposes.AddString("NSPrivacyCollectedDataTypePurposeAnalytics");
}
#endif
private static bool AttributionDefineEnabled()
{
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS) ?? string.Empty;
foreach (var define in defines.Split(';'))
{
if (define.Trim() == iOSInfoPlistPostProcessor.AttributionDefine) return true;
}
return false;
}
}
}