Merge "Doc change: Add best practices docs for permissions and ids." into mnc-docs

This commit is contained in:
Dirk Dougherty
2016-01-14 02:21:00 +00:00
committed by Android (Google) Code Review
12 changed files with 1426 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -0,0 +1,740 @@
page.title=Best Practices for Unique Identifiers
page.metaDescription=How to manage unique identifiers the right way for users.
page.tags=ids, user data
meta.tags="ids", "user data"
page.image=images/cards/card-user-ids_2x.png
page.article=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>In this document</h2>
<ol>
<li><a href="#tenets_of_working_with_android_identifiers">Tenets of Working with
Android Identifiers</a></li>
<li><a href="#version_specific_details_identifiers_in_m">Identifiers in Android 6.0+</a></li>
<li><a href="#working_with_advertising_ids">Working with Advertising IDs</a></li>
<li><a href="#working_with_instance_ids_&_guids">Working with Instance IDs and GUIDs</a></li>
<li><a href="#understand_identifier_characteristics">Understanding Identifier
Characteristics</a>
<ol>
<li><a href="#scope">Scope</a></li>
<li><a href="#resettability_&_persistence">Resettability &amp; persistence</a></li>
<li><a href="#uniqueness">Uniqueness</h3>
<li><a href="#integrity_protection_and_non-repudiability">Integrity protection and
non-repudiability</a></li>
</ol>
</li>
<li><a href="#use_appropriate_identifiers">Common Use Cases and the Identifier to Use</a>
<ol>
<li><a href="#a_track_signed-out_user_preferences">Tracking signed-out user
preferences</a></li>
<li><a href="#b_track_signed-out_user_behavior">Tracking signed-out user behavior</a></li>
<li><a href="#c_generate_signed-out_anonymous_user_analytics">Generating
signed-out/anonymous user analytics</a></li>
<li><a href="#d_track_signed-out_user_conversion">Tracking signed-out user
conversion</a></li>
<li><a href="#e_handle_multiple_installations">Handling multiple installations</a></li>
<li><a href="#f_anti-fraud_enforcing_free_content_limits_detecting_sybil_attacks">Anti-fraud:
Enforcing free content limits / detecting Sybil attacks</a></li>
<li><a href="#g_manage_telephony_&_carrier_functionality">Managing telephony and carrier
functionality</a></li>
<li><a href="#h_abuse_detection_identifying_bots_and_ddos_attacks">Abuse detection:
Identifying bots and DDoS attacks</a></li>
<li><a href="#i_abuse_detection_detecting_high_value_stolen_credentials">Abuse detection:
Detecting high value stolen credentials</a></li>
</ol>
</li>
</ol>
</div>
</div>
<p>
While there are valid reasons why your application may need to identify a
device rather than an instance of the application or an authenticated user on
the device, for the vast majority of applications, the ultimate goal is to
identify a particular <em>installation</em> of your app (not the actual
physical device).
</p>
<p>
Fortunately, identifying an installation on Android is straightforward using
an Instance ID or by creating your own GUID at install time. This document
provides guidance for selecting appropriate identifiers for your application,
based on your use-case.
</p>
<p>
For a general look at Android permissions, please see <a href=
"{@docRoot}training/articles/user-data-overview.html">Permissions
and User Privacy</a>. For specific best practices for
working with Android permissions, please see <a href=
"{@docRoot}training/articles/user-data-permissions.html">Best Practices for
App Permissions</a>.
</p>
<h2 id="tenets_of_working_with_android_identifiers">Tenets of Working with Android Identifiers</h2>
<p>
We recommend that you follow these tenets when working with Android
identifiers:
</p>
<p>
<em><strong>#1: Avoid using hardware identifiers.</strong></em> Hardware
identifiers such as SSAID (Android ID) and IMEI can be avoided in most
use-cases without limiting required functionality.
</p>
<p>
<em><strong>#2: Only use Advertising ID for user profiling or ads
use-cases.</strong></em> When using an <a href=
"https://support.google.com/googleplay/android-developer/answer/6048248?hl=en">
Advertising ID</a>, always respect the <a href=
"https://play.google.com/about/developer-content-policy.html#ADID">Limit Ad
Tracking</a> flag, ensure the identifier cannot be connected to personally
identifiable information (PII) and avoid bridging Advertising ID resets.
</p>
<p>
<em><strong>#3: Use an Instance ID or a privately stored GUID whenever
possible for all other use-cases except payment fraud prevention and
telephony.</strong></em> For the vast majority of non-ads use-cases, an
instance ID or GUID should be sufficient.
</p>
<p>
<em><strong>#4: Use APIs that are appropriate to your use-case to minimize
privacy risk.</strong></em> Use the
<a href="http://source.android.com/devices/drm.html">DRM
API</a> API for high value content
protection and the <a href="{@docRoot}training/safetynet/index.html">SafetyNet
API</a> for abuse prevention. The Safetynet API is
the easiest way to determine whether a device is genuine without incurring
privacy risk.
</p>
<p>
The remaining sections of this guide elaborate on these rules in the context
of developing Android applications.
</p>
<h2 id="version_specific_details_identifiers_in_m">Identifiers in Android 6.0+</h2>
<p>
MAC addresses are globally unique, not user-resettable and survive factory
reset. It is generally not recommended to use MAC address for any form of
user identification. As a result, as of Android M, local device MAC addresses
(for example, Wifi and Bluetooth) <em><strong>are not available via third party
APIs</strong></em>. The {@link android.net.wifi.WifiInfo#getMacAddress WifiInfo.getMacAddress()}
method and the {@link android.bluetooth.BluetoothAdapter#getAddress
BluetoothAdapter.getDefaultAdapter().getAddress()} method will
both return <code>02:00:00:00:00:00</code>..
</p>
<p>
Additionally, you must hold the following permissions to access MAC addresses
of nearby external devices available via Bluetooth and Wifi scans:
</p>
<table>
<tr>
<th><strong>Method/Property</strong></td>
<th><strong>Permissions Required</strong></td>
</tr>
<tr>
<td>
<code><a href="{@docRoot}reference/android/net/wifi/WifiManager.html#getScanResults()">WifiManager.getScanResults()</a></code>
</td>
<td>
<code>ACCESS_FINE_LOCATION</code> or <code>ACCESS_COARSE_LOCATION</code>
</td>
</tr>
<tr>
<td>
<code><a href="{@docRoot}reference/android/bluetooth/BluetoothDevice.html#ACTION_FOUND">BluetoothDevice.ACTION_FOUND</a></code>
</td>
<td>
<code>ACCESS_FINE_LOCATION</code> or <code>ACCESS_COARSE_LOCATION</code>
</td>
</tr>
<tr>
<td>
<code><a href="{@docRoot}reference/android/bluetooth/le/BluetoothLeScanner.html#startScan(android.bluetooth.le.ScanCallback)">BluetoothLeScanner.startScan(ScanCallback)</a></code>
</td>
<td>
<code>ACCESS_FINE_LOCATION</code> or <code>ACCESS_COARSE_LOCATION</code>
</td>
</tr>
</table>
<h2 id="working_with_advertising_ids">Working with Advertising IDs</h2>
<p>
Advertising ID is a user-resettable identifier and is appropriate for Ads
use-cases, but there are some key points to bear in mind when using it:
</p>
<p>
<em><strong>Always respect the users intention in resetting the advertising
ID</strong></em>. Do not bridge user resets by using a more persistent device
identifier or fingerprint to link subsequent Advertising IDs together without
the users consent. The <a href=
"https://play.google.com/about/developer-content-policy.html">Google Play
Developer Content Policy</a> states:
</p>
<div style="padding:.5em 2em;">
<div style="border-left:4px solid #999;padding:0 1em;font-style:italic;">
<p>...upon reset, a new advertising
identifier must not be connected to a previous advertising identifier or data
derived from a previous advertising identifier without the explicit consent
of the user</span></p>
</div>
</div>
<p>
<em><strong>Always respect the associated Interest Based Advertising
flag</strong></em>. Advertising IDs are configurable in that users can limit
the amount of tracking associated with the ID. Always use the <code><a href=
"https://developers.google.com/android/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.Info.html#isLimitAdTrackingEnabled()">
AdvertisingIdClient.Info.isLimitAdTrackingEnabled()</a></code> method to
ensure you are not circumventing your users' wishes. The <a href=
"https://play.google.com/about/developer-content-policy.html">Google Play
Developer Content Policy</a> states:
</p>
<div style="padding:.5em 2em;">
<div style="border-left:4px solid #999;padding:0 1em;font-style:italic;">
<p>...you must abide by a users opt out of
interest-based advertising setting. If a user has enabled this setting, you
may not use the advertising identifier for creating user profiles for
advertising purposes or for targeting users with interest-based advertising.
Allowed activities include contextual advertising, frequency capping,
conversion tracking, reporting and security and fraud detection.</span></p>
</div>
</div>
<p>
<em><strong>Be aware of any privacy or security policies associated with SDKs
you use that are related to Advertising ID use.</strong></em> For example, if
you are using the Google Analytics SDK
<code><a href=
"https://developers.google.com/android/reference/com/google/android/gms/analytics/Tracker.html#enableAdvertisingIdCollection(boolean)">mTracker.enableAdvertisingIdCollection(true)</a></code>
method, make sure to review and adhere to all applicable <a href=
"https://developers.google.com/analytics/devguides/collection/android/v4/policy">
Analytics SDK policies</a>.
</p>
<p>
Also, be aware that the <a href=
"https://play.google.com/about/developer-content-policy.html">Google Play
Developer Content Policy</a> requires that the Advertising ID “must not be
connected to personally-identifiable information or associated with any
persistent device identifier (for example: SSAID, MAC address, IMEI, etc.,)
without the explicit consent of the user.”
</p>
<p>
As an example, suppose you want to collect information to populate database
tables with the following columns:
</p>
<table>
<tr>
<td>
<table>
<tr>
<td class="tab2">
<code>timestamp</code></td>
<td class="tab2">
<code>ad_id</code></td>
<td>
<code><strong>account_id</strong></code></td>
<td class="tab2">
<code>clickid</code></td>
</tr>
</table>
<p>TABLE-01</p>
</td>
<td>
<table>
<tr>
<td>
<code><strong>account_id</strong></code></td>
<td class="tab2">
<code>name</code></td>
<td class="tab2">
<code>dob</code></td>
<td class="tab2">
<code>country</code></td>
</tr>
</table>
<p>TABLE-02</p>
</td>
</tr>
</table>
<p>
In this example, the <code>ad_id</code> column could be joined to PII via the
<code>account_id</code> column in both tables, which would be a violation of
the <a href=
"https://play.google.com/about/developer-content-policy.html">Google Play
Developer Content Policy</a>.
</p>
<p>
Keep in mind that links between Advertiser ID and PII aren't always this
explicit. It's possible to have “quasi-identifiers” that appear in both PII
and Ad ID keyed tables, which also cause problems. For example, assume we
change TABLE-01 and TABLE-02 as follows:
</p>
<table>
<tr>
<td><table>
<tr>
<td>
<code><strong>timestamp</strong></code></td>
<td class="tab2">
<code>ad_id</code></td>
<td>
<code>clickid</code></td>
<td>
<code><strong>dev_model</strong></code></td>
</tr>
</table>
</pre>
<p>TABLE-01</p>
</td>
<td>
<table>
<tr>
<td>
<code><strong>timestamp</strong></code></td>
<td class="tab2">
<code>demo</code></td>
<td class="tab2">
<code>account_id</code></td>
<td>
<code><strong>dev_model</strong></code></td>
<td class="tab2">
<code>name</code></td>
</tr>
</table>
<p>TABLE-02</p>
</td>
</tr>
</table>
<p>
In this case, with sufficiently rare click events, it's still possible to
join between the Advertiser ID TABLE-01 and the PII contained in TABLE-2
using the timestamp of the event and the device model.
</p>
<p>
While it is often difficult to guarantee that no such quasi-identifiers exist
in a dataset, the most obvious join risks can be prevented by generalizing
unique data where possible. In the example, this would mean reducing the
accuracy of the timestamp so that multiple devices with the same model appear
for every timestamp.
</p>
<p>
Other solutions include:
</p>
<ul>
<li><em><strong>Not designing tables that explicitly link PII with Advertising
IDs</strong></em>. In the first example above this would mean not including the
account_id column in TABLE-01.</li>
<li><em><strong>Segregating and monitoring access control lists for users or roles
that have access to both the Advertising ID keyed data and PII</strong></em>. If the
ability to access both sources simultaneously (for example, to perform
a join between two tables) is tightly controlled and audited, it reduces the
risk of association between the Advertising ID and PII. Generally speaking,
controlling access means:
<ol>
<li> Keeping access control lists (ACLs) for Advertiser ID keyed data and PII disjoint to
minimize the number of individuals or roles that are in both ACLs, and</li>
<li> Implementing access logging and auditing to detect and manage any exceptions to
this rule.</li>
</ol>
</li>
</ul>
<p>
For more information on working responsibly with Advertising IDs, please see
the <a href=
"https://support.google.com/googleplay/android-developer/answer/6048248?hl=en">
Advertising ID</a> help center article.
</p>
<h2 id="working_with_instance_ids_&_guids">Working with Instance IDs and GUIDs</h2>
<p>
The most straightforward solution to identifying an application instance
running on a device is to use an Instance ID, and this is the recommended
solution in the majority of non-ads use-cases. Only the app instance for
which it was provisioned can access this identifier, and it's (relatively)
easily resettable because it only persists as long as the app is installed.
</p>
<p>
As a result, Instance IDs provide better privacy properties compared to
non-resettable, device-scoped hardware IDs. They also come with a key-pair
for message signing (and similar actions) and are available on Android, iOS
and Chrome. Please see the <a href=
"https://developers.google.com/instance-id/">What is Instance ID?</a> help
center document for more information.
</p>
<p>
In cases where an Instance ID isn't practical, custom globally unique IDs
(GUIDs) can also be used to uniquely identify an app instance. The simplest
way to do so is by generating your own GUID using the following code.
</p>
<pre>String uniqueID = UUID.randomUUID().toString();</pre>
<p>
Because the identifier is globally unique, it can be used to identify a
specific app instance. To avoid concerns related to linking the identifier
across applications, GUIDs should be stored in internal storage rather than
external (shared) storage. Please see <a href=
"{@docRoot}guide/topics/data/data-storage.html">Storage Options</a> guide for
more information.
</p>
<h2 id="understand_identifier_characteristics">Understanding Identifier Characteristics</h2>
<p>
The Android Operating system offers a number of IDs with different behavior
characteristics and which ID you should use depends on how those following
characteristics work with your use-case. But these characteristics also come
with privacy implications so it's important to understand how these
characteristics play together.
</p>
<h3 id="scope">Scope</h3>
<p>
Identifier scope explains which systems can access the identifier. Android
identifier scope generally comes in three flavors:
</p>
<ul>
<li> <em>Single app</em>. the ID is internal to the app and not accessible to other apps.
<li> <em>Group of apps</em> - the ID is accessible to a pre-defined group of related apps.
<li> <em>Device</em> - the ID is accessible to all apps installed on the device.
</ul>
<p>
The wider the scope granted to an identifier, the greater the risk of it
being used for tracking purposes. Conversely, if an identifier can only be
accessed by a single app instance, it cant be used to track a device across
transactions in different apps.
</p>
<h3 id="resettability_&_persistence">Resettability and persistence</h3>
<p>
Resettability and persistence define the lifespan of the identifier and
explain how it can be reset. Common reset triggers are: in-app resets, resets
via System Settings, resets on launch, and resets on installation. Android
Identifiers can have varying lifespans, but the lifespan is usually related
to how the ID is reset:
</p>
<ul>
<li> <em>Session-only</em> - a new ID is used every time the user restarts the app.
<li> <em>Install-reset</em> - a new ID is used every time user uninstalls and reinstalls the app.
<li> <em>FDR-reset</em> - a new ID is used every time the user factory-resets the device.
<li> <em>FDR-persistent</em> - the ID survives factory reset.
</ul>
<p>
Resettability gives users the ability to create a new ID that is
disassociated from any existing profile information. This is important
because the longer, and more reliably, an identifier persists (e.g. across
factory resets etc.), the greater the risk that the user may be subjected to
long-term tracking. If the identifier is reset upon app reinstall, this
reduces the persistence and provides a means for the ID to be reset, even if
there is no explicit user control to reset it from within the app or the
System Settings.
</p>
<h3 id="uniqueness">Uniqueness</h3>
<p>
Uniqueness establishes the likelihood that identical identifiers exist within
the associated scope. At the highest level, a globally unique identifier will
never have a collision - even on other devices/apps. Otherwise, the level of
uniqueness depends on the size of the identifier and the source of randomness
used to create it. For example, the chance of a collision is much higher for
random identifiers seeded with the calendar date of installation (e.g.,
2015-01-05) than for identifiers seeded with the Unix timestamp of
installation (e.g., 1445530977).
</p>
<p>
In general, user account identifiers can be considered unique (i.e., each
device/account combo has a unique ID). On the other hand, the less the unique
an identifier is within a population (e.g. of devices), the greater the
privacy protection because it's less useful for tracking an individual user.
</p>
<h3 id="integrity_protection_and_non-repudiability">Integrity protection and
non-repudiability</h3>
<p>
An identifier that is difficult to spoof or replay can be used to prove that
the associated device or account has certain properties (e.g. its not a
virtual device used by a spammer). Difficult to spoof identifiers also
provide <em>non-repudiability</em>. If the device signs a message with a
secret key, it is difficult to claim someone elses device sent the message.
Non-repudiability could be something a user wants (e.g. authenticating a
payment) or it could be an undesirable property (e.g. sending a message they
regret).
</p>
<h2 id="use_appropriate_identifiers">Common Use Cases and the Identifier to Use</h2>
<p>
This section provides alternatives to using hardware IDs such as IMEI or
SSAID for the majority of use-cases. Relying on hardware IDs is discouraged
because the user cannot reset them and generally has limited control over
their collection.
</p>
<h3 id="a_track_signed-out_user_preferences">Tracking signed-out user preferences</h3>
<p>
<em>In this case, you are saving per-device state on the server side.</em>
</p>
<p>
<strong>We Recommend</strong>: Instance ID or a GUID.
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
Persisting information through reinstalls is not recommended because users
may want to reset their preferences by reinstalling the app.
</p>
<h3 id="b_track_signed-out_user_behavior">Tracking signed-out user behavior</h3>
<p>
<em>In this case, you have created a profile of a user based on their
behavior across different apps/sessions on the same device.</em>
</p>
<p>
<strong>We Recommend</strong>: Advertising ID.
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
Use of the Advertising ID is mandatory for Advertising use-cases per the
<a href="https://play.google.com/about/developer-content-policy.html">Google
Play Developer Content Policy</a> because the user can reset it.
</p>
<h3 id="c_generate_signed-out_anonymous_user_analytics">Generating signed-out/anonymous user analytics</h3>
<p>
<em>In this case, you are measuring usage statistics and analytics for
signed-out or anonymous users.</em>
</p>
<p>
<strong>We Recommend</strong>: Instance ID; if an Instance ID is
insufficient, you can also use a GUID.
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
An Instance ID or a GUID is scoped to the app that creates it, which prevents
it from being used to track users across apps. It is also easily reset by
clearing app data or reinstalling the app. Creating Instance IDs and GUIDs is
straightforward:
</p>
<ul>
<li> Creating an Instance ID: <code>String iid = InstanceID.getInstance(context).getId()</code>
<li> Creating a GUID: <code>String uniqueID = UUID.randomUUID().toString</code>
</ul>
<p>
Be aware that if you have told the user that the data you are collecting is
anonymous, you should <em><strong>ensure you are not connecting the
identifier to PII</strong></em> or other identifiers that may be linked to
PII.
</p>
<p>
You can also use Google Analytics for Mobile Apps, which offers a solution
for per-app analytics.
</p>
<h3 id="d_track_signed-out_user_conversion">Tracking signed-out user conversion</h3>
<p>
<em>In this case, you are tracking conversions to detect if your marketing
strategy was successful.</em>
</p>
<p>
<strong>We Recommend</strong>: Advertising ID.
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
This is an ads-related use-case which may require an ID that is available
across different apps so using an Advertising ID is the most appropriate
solution.
</p>
<h3 id="e_handle_multiple_installations">Handling multiple installations</h3>
<p>
<em>In this case, you need to identify the correct instance of the app when
it's installed on multiple devices for the same user.</em>
</p>
<p>
<strong>We Recommend</strong>: Instance ID or GUID.
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
Instance ID is designed explicitly for this purpose; its scope is limited to
the app so that it cannot be used to track users across different apps and it
is reset upon app reinstall. In the rare cases where an Instance ID is
insufficient, you can also use a GUID.
</p>
<h3 id="f_anti-fraud_enforcing_free_content_limits_detecting_sybil_attacks">Anti-fraud: Enforcing free content limits / detecting Sybil attacks</h3>
<p>
<em>In this case, you want to limit the number of free content (e.g.
articles) a user can see on a device.</em>
</p>
<p>
<strong>We Recommend</strong>: Instance ID or GUID.
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
Using a GUID or Instance ID forces the user to reinstall the app in order to
overcome the content limits, which is a sufficient burden to deter most
people. If this is not sufficient protection, Android provides a
<a href="http://source.android.com/devices/drm.html">DRM API</a>
which can be used to limit access to content.
</p>
<h3 id="g_manage_telephony_&_carrier_functionality">Managing telephony and carrier functionality</h3>
<p>
<em>In this case, your app is interacting with the device's phone and texting
functionality.</em>
</p>
<p>
<strong>We Recommend</strong>: IMEI, IMSI, and Line1.
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
Leveraging hardware identifiers is acceptable if it is required for
telephony/carrier related functionality; for example, switching between
cellular carriers/SIM slots or delivering SMS messages over IP (for Line1) -
SIM-based user accounts. But it's important to note that in Android 6.0+
these identifiers can only be used via a runtime permission and that users
may toggle off this permission so your app should handle these exceptions
gracefully.
</p>
<h3 id="h_abuse_detection_identifying_bots_and_ddos_attacks">Abuse detection:
Identifying bots and DDoS attacks</h3>
<p>
<em>In this case, you are trying to detect multiple fake devices attacking
your backend services.</em>
</p>
<p>
<strong>We Recommend:</strong> The Safetynet API.
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
An identifier in isolation does little to indicate that a device is genuine.
You can verify that a request comes from a genuine Android device (as opposed
to an emulator or other code spoofing another device) using the Safetynet
API's <code>SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)</code>
method to verify the integrity of a device making a request. For more
detailed information, please see <a href=
"{@docRoot}training/safetynet/index.html">Safetynet's API documentation</a>.
</p>
<h3 id="i_abuse_detection_detecting_high_value_stolen_credentials">Abuse detection:
Detecting high value stolen credentials</h3>
<p>
<em>In this case, you are trying to detect if a single device is being used
multiple times with high-value, stolen credentials (e.g. to make fraudulent
payments).</em>
</p>
<p>
<strong>We Recommend</strong>: IMEI/IMSI (requires <code>PHONE</code>
permission group in Android 6.0 (API level 23) and higher.)
</p>
<p>
<strong>Why this Recommendation?</strong>
</p>
<p>
With stolen credentials, devices can be used to monetize multiple high value
stolen credentials (such as tokenized credit cards). In these scenarios,
software IDs can be reset to avoid detection, so hardware identifiers may be
used.
</p>

View File

@@ -0,0 +1,266 @@
page.title=Permissions and User Privacy
page.metaDescription=An overview of permissions on Android and how to manage them.
page.tags="user data","permissions","identifiers"
page.image=images/cards/card-user_2x.png
page.article=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>In this document</h2>
<ol>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#permission_groups">Permission Groups</a></li>
<li><a href="#permission_requests_and_app_downloads">Permission
Requests and App Downloads</a></li>
<li><a href="#permission_requests_trend_downward">Permission Requests
Trend Downward</a></li>
</ol>
<h2>You should also read</h2>
<ol>
<li><a href="{@docRoot}guide/topics/security/permissions.html">System Permissions</a></li>
<li><a href="{@docRoot}training/permissions/index.html">Working with System
Permissions</a></li>
</ol>
</div>
</div>
<p>
Permissions protect sensitive information available from a device and should
only be used when access to information is necessary for the functioning of
your app.
</p>
<p>
This document provides a high-level overview on how permissions work in
Android so you can make better, more informed decisions about the permissions
you're requesting. The information in this document is not use-case specific
and avoids complex, low-level discussions of the underlying code.
</p>
<p>
For specific recommendations on how to manage permissions, please see
<a href="{@docRoot}training/articles/user-data-permissions.html">Best
Practices for App Permissions</a>. For best practices on using unique
identifiers on Android, please see <a href=
"{@docRoot}training/articles/user-data-ids.html">Best Practices for Unique
Identifiers</a>. For details on how to work with permissions in your code,
see <a href="{@docRoot}training/permissions/index.html">Working with System
Permissions</a>.
</p>
<h2 id="introduction">Introduction</h2>
<p>
Every Android application must have a <em>manifest file</em> that presents
essential information about the app to the Android system. The Android system
also requires apps to request permission when they want to access sensitive
device or user information, and these requests must be documented in advance
as a part of your app's manifest. Moreover, accessing sensitive information
can affect user behavior, so it's important to make sure you are only making
permission requests when that information is necessary for the functioning of
your app.
</p>
<h2 id="permission_groups">Permission Groups</h2>
<p>
Permissions in Android are organized into <code><a href=
"{@docRoot}guide/topics/security/permissions.html#perm-groups">permission
groups</a></code> that organize, and group, permissions related to a device's
capabilities or features. Under this system, permission requests are handled
at the group level and a <em><strong>single permission group</strong></em>
corresponds to <em><strong>several permission declarations</strong></em> in
the app manifest; for example, the SMS group includes both the
<code>READ_SMS</code> and the <code>WRITE_SMS</code> declarations.
</p>
<div class="wrap">
<img src="{@docRoot}images/training/articles/user-data-overview-permissions-flow01.jpg">
</div>
<p>
This arrangement is simpler and more informative for users; once an app is
granted permission to access the group, it can use API calls within that
group and users with auto-update enabled will not be asked for additional
permissions because they have already granted access to the group. Grouping
permissions in this way enables the user to make more meaningful and informed
choices, without being overwhelmed by complex and technical permission
requests.
</p>
<p>
This also means that when you request access to a particular API call or
query a content provider behind a permission, the user will be presented with
a request to grant permission for the whole group rather than the specific
API call. For example, if you request the <code>MANAGE_ACOUNTS</code>
permission, the user will be asked to grant access to the <em>Identity</em>
group which is composed of the <code>GET_ACCOUNTS</code>,
<code>READ_PROFILE</code>, and <code>WRITE_PROFILE</code> permissions, and
all their associated methods.
</p>
<div class="wrap">
<img src="{@docRoot}images/training/articles/user-data-overview-permissions-flow02.jpg">
</div>
<p>
One consequence of grouping permissions is that a single API call within your
app can have a multiplying effect in terms of the number of permissions
requested by your app.
</p>
<ol>
<li>API Call →</li>
<li stydle="margin-left:.5em;">Triggers a specific <em>Permission Group</em> access
request →</li>
<li stydle="margin-left:1em;">Successful request grants access to all permissions in
group (if auto-update
enabled) →</li>
<li stydle="margin-left:1.5em;">Each permission grants access to all APIs under that
permission</li>
</ol>
<p>
As another example, let's assume your application uses one or more <a href=
"{@docRoot}reference/android/telephony/TelephonyManager.html"><code>TelephonyManager</code></a>
methods, such as:
</p>
<pre class="prettyprint">
TelephonyManager.getDeviceId()
TelephonyManager.getSubscriberId()
TelephonyManager.getSimSerialNumber()
TelephonyManager.getLine1Number()
TelephonyManager.getVoiceMailNumber()
</pre>
<p>
To use these methods, the <code>READ_PHONE_STATE</code> permission must be
declared in the app's manifest, and the associated permission group,
<em>Device ID and Call information</em>, will be surfaced to the user. This
is important, because it means the user will be asked to grant permission for
the relevant group and all its associated permissions and API calls, rather
than for the specific API call you're requesting.
</p>
<p>For a full mapping between permissions and their associated permission groups,
please refer to the appropriate version-specific documentation below:</p>
<ul>
<!--<li> <a href="">pre-M Android OS versions</a>.</li> -->
<li> <a href="{@docRoot}guide/topics/security/permissions.html#perm-groups">Permission
groups, Android 6.0 Marshmallow (API level 23) and later</a>.</li>
</ul>
<h2 id="permission_requests_and_app_downloads">Permission Requests and App Downloads</h2>
<div style="padding:.5em 2em;">
<div style="border-left:4px solid #999;padding:0 1em;font-style:italic;">
<p><em>I'm currently using the READ_PHONE_STATE permission in Android to pause my
media player when there's a call, and to resume playback when the call is over.
The permission seems to scare a lot of people</em>...<span
style="font-size:.8em;color:#777"><sup><em><a
href="#references" style="color:#777;padding-left:.1em;">1</a></em></span></p>
</div>
</div>
<p>
Research shows that among apps that are otherwise identical (e.g.,
functionality, brand recognition), requesting fewer permissions leads to more
downloads. Publicly available sources exist that assign grades to apps based
on their permissions usage and allow users to compare related apps by score;
such grades exist for many of the current Android apps and users pay close
attention to the related rankings.
</p>
<p>
One study<span style="font-size:.8em;color:#777"><sup><em><a href=
"#references" style=
"color:#777;padding-left:.1em;">2</a></em></sup></span><sup>, in which users
were shown two unbranded apps with similar ratings that had the same
functionality but different sets of permission requests, showed that users
were, on average, 3 times more likely to install the app with fewer
permissions requests. And a similar study<span style=
"font-size:.8em;color:#777"><sup><em><a href="#references" style=
"color:#777;padding-left:.1em;">3</a></em></sup> showed that users are 1.7
times more likely, on average, to select the application with fewer
permission requests.</span></sup>
</p>
<p>
<sup>Finally, permissions usage is not evenly distributed across apps within
a similar category of Play apps. For example, 39.3% of arcade game apps in
the Play store request no permissions that are surfaced to the user while
only 1.5% of arcade games request the Phone permission group (see Figure
1).</sup>
</p>
<div class="wrap">
<div class="cols">
<div class="col-16of16">
<img src="{@docRoot}images/training/articles/user-data-overview-permissions-groups.png">
<p class="figure-caption"><strong>Figure 1.</strong> Distribution of
permission groups use across Arcade Games category.</p>
</div>
</div>
</div>
<p>
Users comparing your app to other similar apps may determine that it is
making unusual permission requests for that category - in this case, Arcade
Games apps accessing the <em>Phone</em> permission group. As a result, they
may install a similar app in that category that avoids those
requests.<span style="font-size:.8em;color:#777"><sup><em><a href=
"#references" style="color:#777;padding-left:.1em;">4</a></em></sup></span>
</p>
<h2 id="permission_requests_trend_downward">Permission Requests Trend Downward</h2>
<p>
A recent analysis of Play store apps over time indicated that many developers
trim permissions after first publishing their apps, suggesting that they may
be employing more caution around which permission groups they declare.
</p>
<div class="wrap">
<div class="cols">
<div class="col-16of16">
<img src="{@docRoot}images/training/articles/user-data-overview-permissions-usage.jpg">
<p class="figure-caption"><strong>Figure 2.</strong> Developer usage of popular
permissions has decreased over time.</p>
</div>
</div>
</div>
<p>
The graph in <em>Figure 2</em> illustrates this trend. There has been a
steady decrease in the average percentage of developers' apps requesting at
least one of the three most popular permissions in the Play store
(<code>READ_PHONE_STATE</code>, <code>ACCESS_FINE_LOCATION</code>, and
<code>ACCESS_COARSE_LOCATION</code>). These results indicate that developers
are reducing the permissions their apps request in response to user behavior.
</p>
<p>
The bottom line is that providing the same functionality to the user with
minimal access to sensitive information means more downloads for your app.
For specific recommendations on how to achieve this, please see <a href=
"{@docRoot}training/articles/user-data-permissions.html">Best Practices for
Application Permissions</a>.
</p>
<h2 id="references">References</h2>
<p>[1] Developer quote on StackOverflow. <em>(<a
href="http://stackoverflow.com/questions/24374701/alternative-to-read-phone-state-permission-for-getting-notified-of-call">source</a>)</em></p>
<p>[2] <em>Using Personal Examples to Improve Risk Communication for Security and Privacy Decisions</em>, by M. Harbach, M. Hettig, S. Weber, and M. Smith. In Proceedings of ACM CHI 2014.</p>
<p>[3] <em>Modeling Users Mobile App Privacy Preferences: Restoring Usability in a Sea of Permission Settings</em>, by J. Lin B. Liu, N. Sadeh and J. Hong. In Proceedings of SOUPS 2014.</p>
<p>[4] <em>Teens and Mobile Apps Privacy. (<a href="http://www.pewinternet.org/files/old-media/Files/Reports/2013/PIP_Teens%20and%20Mobile%20Apps%20Privacy.pdf">source</a>)</em></p>

View File

@@ -0,0 +1,381 @@
page.title=Best Practices for App Permissions
page.metaDescription=How to manage permissions to give users context and control.
page.tags=permissions, user data
meta.tags="permissions", "user data"
page.image=images/cards/card-user-permissions_2x.png
page.article=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>In this document</h2>
<ol>
<li><a href="#tenets_of_working_with_android_permissions">Tenets</a></li>
<li><a href="#version_specific_details_permissions_in_m">Permissions in Android
6.0+</h2></a></li>
<li><a href="#avoid_requesting_unnecessary_permissions">Avoid Requesting
Unnecessary Permissions</h2></a>
<ol>
<li><a href="#a_camera_contact_access_with_real-time_user_requests">Camera/Contact
access with realtime user requests</a></li>
<li><a href="#b_running_in_the_background_after_losing_audio_focus">Running in
the background after losing audio focus</a></li>
<li><a href="#c_determine_the_device_your_instance_is_running_on">Determine the
device your instance is running on</a></li>
<li><a href="#d_create_a_unique_identifier_for_advertising_or_user_analytics">
Create a unique identifier for advertising or user analytics</a></li>
</ol>
</li>
<li><a href="#know_the_libraries_you're_working_with">Know the Libraries You're
Working With</a></li>
<li><a href="#be_transparent">Be Transparent</a></li>
</ol>
<h2>You should also read</h2>
<ol>
<li><a href="{@docRoot}guide/topics/security/permissions.html">System Permissions</a></li>
<li><a href="{@docRoot}training/permissions/index.html">Working with System
Permissions</a></li>
</ol>
</div>
</div>
<p>
Permission requests protect sensitive information available from a device and
should only be used when access to information is necessary for the
functioning of your app. This document provides tips on ways you might be
able to achieve the same (or better) functionality without requiring access
to such information; it is not an exhaustive discussion of how permissions
work in the Android operating system.
</p>
<p>
For a more general look at Android permissions, please see <a href=
"{@docRoot}training/articles/user-data-overview.html">Permissions
and User Privacy</a>. For details on how to work with permissions in your code,
see <a href="{@docRoot}training/permissions/index.html">Working with System Permissions</a>.
For best practices for working with unique identifiers, please see <a href=
"{@docRoot}training/articles/user-data-ids.html">Best Practices for
Unique Identifiers</a>.
</p>
<h2 id="tenets_of_working_with_android_permissions">Tenets of Working
with Android Permissions</h2>
<p>
We recommend following these tenets when working with Android permissions:
</p>
<p>
<em><strong>#1: Only use the permissions necessary for your app to
work</strong></em>. Depending on how you are using the permissions, there may
be another way to do what you need (system intents, identifiers,
backgrounding for phone calls) without relying on access to sensitive
information.
</p>
<p>
<em><strong>#2: Pay attention to permissions required by
libraries.</strong></em> When you include a library, you also inherit its
permission requirements. You should be aware of what you're including, the
permissions they require, and what those permissions are used for.
</p>
<p>
<em><strong>#3: Be transparent.</strong></em> When you make a permissions
request, be clear about what youre accessing, and why, so users can make
informed decisions. Make this information available alongside the permission
request including install, runtime, or update permission dialogues.
</p>
<p>
<em><strong>#4: Make system accesses explicit.</strong></em> Providing
continuous indications when you access sensitive capabilities (for example, the
camera or microphone) makes it clear to users when youre collecting data and
avoids the perception that you're collecting data surreptitiously.
</p>
<p>
The remaining sections of this guide elaborate on these rules in the context
of developing Android applications.
</p>
<h2 id="version_specific_details_permissions_in_m">Permissions in Android 6.0+</h2>
<p>
Android 6.0 Marshmallow introduced a <a href=
"{@docRoot}training/permissions/requesting.html">new permissions model</a> that
lets apps request permissions from the user at runtime, rather than prior to
installation. Apps that support the new model request permissions when the app
actually requires the services or data protected by the services. While this
doesn't (necessarily) change overall app behavior, it does create a few
changes relevant to the way sensitive user data is handled:
</p>
<p>
<em><strong>Increased situational context</strong></em>: Users are be
prompted at runtime, in the context of your app, for permission to access the
functionality covered by those permission groups. Users are be more sensitive to
the context in which the permission is requested, and if theres a mismatch
between what you are requesting and the purpose of your app, it's even
more important to provide detailed explanation to the user as to why youre
requesting the permission; whenever possible, you should provide an
explanation of your request both at the time of the request and in a
follow-up dialog if the user denies the request.
</p>
<p>
<em><strong>Greater flexibility in granting permissions</strong></em>: Users
can deny access to individual permissions at the time theyre requested
<em>and</em> in settings, but they may still be surprised when functionality is
broken as a result. Its a good idea to monitor how many users are denying
permissions (e.g. using Google Analytics) so that you can either refactor
your app to avoid depending on that permission or provide a better
explanation of why you need the permission for your app to work properly. You
should also make sure that your app handles exceptions created when users
deny permission requests or toggle off permissions in settings.
</p>
<p>
<em><strong>Increased transactional burden</strong></em>: Users will be asked
to grant access for permission groups individually and not as a set. This
makes it extremely important to minimize the number of permissions youre
requesting because it increases the user burden for granting permissions and
increases the probability that at least one of the requests will be denied.
</p>
<h2 id="avoid_requesting_unnecessary_permissions">Avoid Requesting
Unnecessary Permissions</h2>
<p>
This section provides alternatives to common use-cases that will help you
limit the number of permission requests you make. Since the number and type
of user-surfaced permissions requested affects downloads compared to other
similar apps requesting fewer permissions, its best to avoid requesting
permissions for unnecessary functionality.
</p>
<h3 id="a_camera_contact_access_with_real-time_user_requests">Camera/contact
access with realtime user requests</h3>
<p>
<em>In this case, you need occasional access to the device's camera or
contact information and dont mind the user being asked every time you need
access.</em>
</p>
<p>
If your requirement for access to user data is infrequent &mdash; in other
words, it's not unacceptably disruptive for the user to be presented with a
runtime dialogue each time you need to access data &mdash; you can use an
<em>intent based request</em>. Android provides system intents that
applications can use without requiring permissions because the user chooses
what, if anything, to share with the app at the time the intent based request
is issued.
</p>
<p>
For example, an intent action type of <code><a href=
"{@docRoot}reference/android/provider/MediaStore.html#ACTION_IMAGE_CAPTURE">MediaStore.ACTION_IMAGE_CAPTURE</a></code>
or <code><a href=
"{@docRoot}reference/android/provider/MediaStore.html#ACTION_VIDEO_CAPTURE">MediaStore.ACTION_VIDEO_CAPTURE</a></code>
can be used to capture images or videos without directly using the <a href=
"{@docRoot}reference/android/hardware/Camera.html">Camera</a> object (or
requiring the permission). In this case, the system intent will ask for the
users permission on your behalf every time an image is captured.
</p>
<h3 id="b_running_in_the_background_after_losing_audio_focus">Running in
the background after losing audio focus</h3>
<p>
<em>In this case, your application needs to go into the background when the
user gets a phone call and refocus only once the call stops.</em>
</p>
<p>
The common approach in these cases - for example, a media player muting or
pausing during a phone call - is to listen for changes in the call state
using <code>PhoneStateListener</code> or listening for the broadcast of
<code>android.intent.action.PHONE_STATE</code>. The problem with this
solution is that it requires the <code>READ_PHONE_STATE</code> permission,
which forces the user to grant access to a wide cross section of sensitive
data such as their device and SIM hardware IDs and the phone number of the
incoming call.
</p>
<p>
You can avoid this by requesting <code>AudioFocus</code> for your app, which
doesn't require explicit permissions (because it does not access sensitive
information). Simply put the code required to background your audio in the
<code><a href=
"{@docRoot}reference/android/media/AudioManager.OnAudioFocusChangeListener.html#onAudioFocusChange(int)">
onAudioFocusChange()</a></code> event handler and it will run automatically
when the OS shifts its audio focus. More detailed documentation on how to do
this can be found <a href=
"{@docRoot}training/managing-audio/audio-focus.html">here</a>.
</p>
<h3 id="c_determine_the_device_your_instance_is_running_on">Determine the
device your instance is running on</h3>
<p>
<em>In this case, you need a unique identifier to determine which device the
instance of your app is running on.</em>
</p>
<p>
Applications may have device-specific preferences or messaging (e.g., saving
a device-specific playlist for a user in the cloud so that they can have a
different playlist for their car and at home). A common solution is to
leverage device identifiers such as <code>Device IMEI</code>, but this
requires the <code>Device ID and call information</code>
permission group (<code>PHONE</code> in M+). It also uses an identifier which
cannot be reset and is shared across all apps.
</p>
<p>
There are two alternatives to using these types of identifiers:
</p>
<ol>
<li> Use the <code>com.google.android.gms.iid</code> InstanceID API.
<code>getInstance(Context context).getID()<strong></code> </strong>will return a
unique device identifier for your application instance. The
result is an app instance scoped identifier that can be used as a key when
storing information about the app and is reset if the user re-installs the app.
<li> Create your own identifier thats scoped to your apps storage using basic
system functions like <a
href="{@docRoot}reference/java/util/UUID.html#randomUUID()"><code>randomUUID()</code></a>.</li>
</ol>
<h3 id="d_create_a_unique_identifier_for_advertising_or_user_analytics">Create a unique
identifier for advertising or user analytics</h3>
<p>
<em>In this case, you need a unique identifier for building a profile for
users who are not signed in to your app (e.g., for ads targeting or measuring
conversions).</em>
</p>
<p>
Building a profile for advertising and user analytics sometimes requires an
identifier that is shared across other applications. Common solutions for
this involve leveraging device identifiers such as <code>Device IMEI</code>,
which requires the <code>Device ID</code> <code>and call information</code>
permission group (<code>PHONE</code> in API level 23+) and cannot be reset by
the user. In any of these cases, in addition to using a non-resettable
identifier and requesting a permission that might seem unusual to users, you
will also be in violation of the <a href=
"https://play.google.com/about/developer-content-policy.html">Play Developer
Program Policies</a>.
</p>
<p>
Unfortunately, in these cases using the
<code>com.google.android.gms.iid</code> InstanceID API or system functions to
create an app-scoped ID are not appropriate solutions because the ID may need
to be shared across apps. An alternative solution is to use the
<code>Advertising Identifier</code> available from the <code><a href=
"{@docRoot}reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.Info.html">
AdvertisingIdClient.Info</a></code> class via the <code>getId()</code>
method. You can create an <code>AdvertisingIdClient.Info</code> object using
the <code>getAdvertisingIdInfo(Context)</code> method and call the
<code>getId()</code> method to use the identifier. <em><strong>Note that this
method is blocking</strong></em>, so you should not call it from the main
thread; a detailed explanation of this method is available <a href=
"{@docRoot}google/play-services/id.html">here</a>.
</p>
<h2 id="know_the_libraries_you're_working_with">Know the Libraries You're
Working With</h2>
<p>
Sometimes permissions are required by the libraries you use in your app. For
example, ads and analytics libraries may require access to the
<code>Location</code> or <code>Identity</code> permissions groups to
implement the required functionality. But from the users point of view, the
permission request comes from your app, not the library.
</p>
<p>
Just as users select apps that use fewer permissions for the same
functionality, developers should review their libraries and select
third-party SDKs that are not using unnecessary permissions. For example, try
to avoid libraries that require the <code>Identity</code> permission group
unless there is a clear user-facing reason why the app needs those permissions.
In particular, for libraries that provide location functionality, make sure you
are not required to request the <code>FINE_LOCATION</code> permission unless
you are using location-based targeting functionality.
</p>
<h2 id="be_transparent">Be Transparent</h2>
<p>You should inform your users about what youre accessing and why. Research shows
that users are much less uncomfortable with permissions requests if they know
why the app needs them. A user study showed that:</p>
<div style="padding:.5em 2em;">
<div style="border-left:4px solid #999;padding:0 1em;font-style:italic;">
<p>...a users willingness to grant a given permission to a given mobile app is
strongly influenced by the purpose associated with such a permission. For
instance a users willingness to grant access to his or her location will vary
based on whether the request is required to support the apps core
functionality or whether it is to share this information with an advertising
network or an analytics company.<span
style="font-size:.8em;color:#777"><sup><em><a
href="#references" style="color:#777;padding-left:.1em;">1</a></em></sup></span></p>
</div>
</div>
<p>
Based on his groups research, Professor Jason Hong from CMU concluded that,
in general:
</p>
<div style="padding:.5em 2em;">
<div style="border-left:4px solid #999;padding:0 1em;font-style:italic;">
<p>...when people know why an app is using something as sensitive as their location &mdash;
for example, for targeted advertising &mdash; it makes them more comfortable than
when simply told an app is using their location.<span
style="font-size:.8em;color:#777"><sup><em><a
href="#references" style="color:#777;padding-left:.1em;">1</a></em></sup></span></p>
</div>
</div>
<p>
As a result, if youre only using a fraction of the API calls that fall under
a permission group, it helps to explicitly list which of those permissions
you're using, and why. For example:
</p>
<ul>
<li> If youre only using coarse location, let the user know this in your app
description or in help articles about your app. </li>
<li> If you need access to SMS messages to receive authentication codes that
protect the user from fraud, let the user know this in your app description
and/or the first time you access the data.</li>
</ul>
<p>
Under certain conditions, it's also advantageous to let users know about
sensitive data accesses in real-time. For example, if youre accessing the
camera or microphone, its usually a good idea to let the user know with a
notification icon somewhere in your app, or in the notification tray (if the
application is running in the background), so it doesn't seem like you're
collecting data surreptitiously.
</p>
<p>
Ultimately, if you need to request a permission to make something in your app
work, but the reason is not clear to the user, find a way to let the user
know why you need the most sensitive permissions.
</p>
<h2 id="references">References</h2>
<p>
[1] <em>Modeling Users Mobile App Privacy Preferences: Restoring Usability
in a Sea of Permission Settings</em>, by J. Lin B. Liu, N. Sadeh and J. Hong.
In Proceedings of SOUPS 2014.
</p>

View File

@@ -0,0 +1,9 @@
page.title=Best Practices for Permissions and Identifiers
page.trainingcourse=true
@jd:body
<p>The articles below highlight key guidelines for using permissions
and identifiers properly in your apps.</p>

View File

@@ -2083,6 +2083,36 @@ results."
</li>
<!-- End security and user info -->
<li class="nav-section">
<div class="nav-section-header">
<a href="<?cs var:toroot ?>training/best-permissions-ids.html">
<span class="small">Best Practices for</span><br/>
Permissions &amp; Identifiers
</a>
</div>
<ul>
<li>
<a href="<?cs var:toroot ?>training/articles/user-data-overview.html"
description=
"Overview of app permissions on Android and how they affect your users."
>Permissions and User Privacy</a>
</li>
<li>
<a href="<?cs var:toroot ?>training/articles/user-data-permissions.html"
description=
"How to manage permissions the right way for users."
>Best Practices for App Permissions</a>
</li>
<li>
<a href="<?cs var:toroot ?>training/articles/user-data-ids.html"
description=
"Unique identifiers available and how to choose the right one for your use case."
>Best Practices for Unique Identifiers</a>
</li>
</ul>
</li>
<!-- End Permissions and identifiers -->
<li class="nav-section">
<div class="nav-section-header">
<a href="<?cs var:toroot ?>training/testing.html">