Merge "DOC CHANGE: Android Beam training class" into jb-mr2-docs

This commit is contained in:
Joe Malin
2013-10-09 23:08:15 +00:00
committed by Android (Google) Code Review
4 changed files with 685 additions and 3 deletions

View File

@@ -0,0 +1,61 @@
page.title=Sharing Files with NFC
trainingnavtop=true
startpage=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>Dependencies and prerequisites</h2>
<ul>
<li>Android 4.1 (API Level 16) or higher</li>
<li>At least two NFC-enabled Android devices (NFC is not supported in the emulator)</li>
</ul>
<h2>You should also read</h2>
<ul>
<li>
<a href="{@docRoot}guide/topics/data/data-storage.html#filesExternal"
>Using the External Storage</a>
</li>
</ul>
</div>
</div>
<p>
Android allows you to transfer large files between devices using the Android Beam file transfer
feature. This feature has a simple API and allows users to start the transfer process by simply
touching devices. In response, Android Beam file transfer automatically copies files from one
device to the other and notifies the user when it's finished.
</p>
<p>
While the Android Beam file transfer API handles large amounts of data, the Android Beam NDEF
transfer API introduced in Android 4.0 (API level 14) handles small amounts of data such as
URIs or other small messages. In addition, Android Beam is only one of the features available
in the Android NFC framework, which allows you to read NDEF messages from NFC tags. To learn
more about Android Beam, see the topic
<a href="{@docRoot}guide/topics/connectivity/nfc/nfc.html#p2p"
>Beaming NDEF Messages to Other Devices</a>. To learn more about the NFC framework, see the
<a href="{@docRoot}guide/topics/connectivity/nfc/index.html"
>Near Field Communication</a> API guide.
</p>
<h2>Lessons</h2>
<dl>
<dt>
<b><a href="send-files.html">Sending Files to Another Device</a></b>
</dt>
<dd>Learn how to set up your app to send files to another device.</dd>
<dt>
<b><a href="receive-files.html">Receiving Files from Another Device</a></b>
</dt>
<dd>
Learn how to set up your app to receive files sent by another device.
</dd>
</dl>

View File

@@ -0,0 +1,313 @@
page.title=Receiving Files from Another Device
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#IntentFilter">Respond to a Request to Display Data</a></li>
<li><a href="#RequestPermissions">Request File Permissions</a></li>
<li><a href="#GetFilePath">Get the Directory for Copied Files</a></li>
</ol>
<h2>You should also read</h2>
<ul>
<li>
<a href="{@docRoot}guide/topics/providers/content-provider-basics.html#ContentURIs"
>Content URIs</a>
</li>
<li>
<a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a>
</li>
<li>
<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Notifications</a>
</li>
<li>
<a href="{@docRoot}guide/topics/data/data-storage.html#filesExternal"
>Using the External Storage</a>
</li>
</ul>
</div>
</div>
<p>
Android Beam file transfer copies files to a special directory on the receiving device. It also
scans the copied files using the Android Media Scanner and adds entries for media files to
the {@link android.provider.MediaStore} provider. This lesson shows you how to respond when the
file copy is complete, and how to locate the copied files on the receiving device.
</p>
<h2 id="IntentFilter">Respond to a Request to Display Data</h2>
<p>
When Android Beam file transfer finishes copying files to the receiving device, it posts a
notification containing an {@link android.content.Intent} with the action
{@link android.content.Intent#ACTION_VIEW ACTION_VIEW}, the MIME type of the first file that
was transferred, and a URI that points to the first file. When the user clicks the notification,
this intent is sent out to the system. To have your app respond to this intent, add an
<code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"
>&lt;intent-filter&gt;</a></code> element for the
<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"
>&lt;activity&gt;</a></code> element of the {@link android.app.Activity} that should respond.
In the <code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"
>&lt;intent-filter&gt;</a></code> element, add the following child elements:
</p>
<dl>
<dt>
<code><a href="{@docRoot}guide/topics/manifest/action-element.html"
>&lt;action android:name="android.intent.action.VIEW" /&gt;</a></code>
</dt>
<dd>
Matches the {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent sent from the
notification.
</dd>
<dt>
<code><a href="{@docRoot}guide/topics/manifest/category-element.html"
>&lt;category android:name="android.intent.category.CATEGORY_DEFAULT" /&gt;</a></code>
</dt>
<dd>
Matches an {@link android.content.Intent} that doesn't have an explicit category.
</dd>
<dt>
<code><a href="{@docRoot}guide/topics/manifest/data-element.html"
>&lt;data android:mimeType="<i>mime-type</i>" /&gt;</a></code>
</dt>
<dd>
Matches a MIME type. Specify only those MIME types that your app can handle.
</dd>
</dl>
<p>
For example, the following snippet shows you how to add an intent filter that
triggers the activity <code>com.example.android.nfctransfer.ViewActivity</code>:
</p>
<pre>
&lt;activity
android:name="com.example.android.nfctransfer.ViewActivity"
android:label="Android Beam Viewer" &gt;
...
&lt;intent-filter&gt;
&lt;action android:name="android.intent.action.VIEW"/&gt;
&lt;category android:name="android.intent.category.DEFAULT"/&gt;
...
&lt;/intent-filter&gt;
&lt;/activity&gt;
</pre>
<p class="note">
<strong>Note:</strong> Android Beam file transfer is not the only source of an
{@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent. Other apps on the receiving
device can also send an {@link android.content.Intent} with this action.
Handling this situation is discussed in the section <a href="#GetDirectory"
>Get the directory from a content URI</a>.
</p>
<h2 id="RequestPermissions">Request File Permissions</h2>
<p>
To read files that Android Beam file transfer copies to the device, request the permission
{@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE}. For example:
</p>
<pre>
&lt;uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /&gt;</pre>
<p>
If you want to copy transferred files to your app's own storage area, request the permission
{@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE} instead.
{@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE} includes
{@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE}.
</p>
<p class="note">
<strong>Note:</strong> As of Android 4.2.2 (API level 17), the permission
{@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE} is
only enforced if the user chooses to do so. Future versions of the platform may require this
permission in all cases. To ensure forward compatibility, request the permission now, before it
becomes required.
</p>
<p>
Since your app has control over its internal storage area, you don't need to request
write permission to copy a transferred file to your internal storage area.
</p>
<h2 id="GetFilePath">Get the Directory for Copied Files</h2>
<p>
Android Beam file transfer copies all the files in a single transfer to one directory
on the receiving device. The URI in the content {@link android.content.Intent} sent by the
Android Beam file transfer notification points to the first transferred file. However, your
app may also receive an {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent from a
source other than Android Beam file transfer. To determine how you should handle the incoming
{@link android.content.Intent}, you need to examine its scheme and authority.
</p>
<p>
To get the scheme for the URI, call {@link android.net.Uri#getScheme() Uri.getScheme()}. The
following code snippet shows you how to determine the scheme and handle the URI accordingly:
</p>
<pre>
public class MainActivity extends Activity {
...
// A File object containing the path to the transferred files
private File mParentPath;
// Incoming Intent
private Intent mIntent;
...
/*
* Called from onNewIntent() for a SINGLE_TOP Activity
* or onCreate() for a new Activity. For onNewIntent(),
* remember to call setIntent() to store the most
* current Intent
*
*/
private void handleViewIntent() {
...
// Get the Intent action
mIntent = getIntent();
String action = mIntent.getAction();
/*
* For ACTION_VIEW, the Activity is being asked to display data.
* Get the URI.
*/
if (TextUtils.equals(action, Intent.ACTION_VIEW)) {
// Get the URI from the Intent
Uri beamUri = mIntent.getData();
/*
* Test for the type of URI, by getting its scheme value
*/
if (TextUtils.equals(beamUri.getScheme(), "file")) {
mParentPath = handleFileUri(beamUri);
} else if (TextUtils.equals(
beamUri.getScheme(), "content")) {
mParentPath = handleContentUri(beamUri);
}
}
...
}
...
}
</pre>
<h3>Get the directory from a file URI</h3>
<p>
If the incoming {@link android.content.Intent} contains a file URI, the URI contains the
absolute file name of a file, including the full directory path and file name. For Android Beam
file transfer, the directory path points to the location of the other transferred files, if
any. To get the directory path, get the path part of the URI, which contains all of the URI
except the <code>file:</code> prefix. Create a {@link java.io.File} from the path part, then
get the parent path of the {@link java.io.File}:
</p>
<pre>
...
public String handleFileUri(Uri beamUri) {
// Get the path part of the URI
String fileName = beamUri.getPath();
// Create a File object for this filename
File copiedFile = new File(fileName);
// Get a string containing the file's parent directory
return copiedFile.getParent();
}
...
</pre>
<h3 id="GetDirectory">Get the directory from a content URI</h3>
<p>
If the incoming {@link android.content.Intent} contains a content URI, the URI may point to a
directory and file name stored in the {@link android.provider.MediaStore} content provider. You
can detect a content URI for {@link android.provider.MediaStore} by testing the URI's
authority value. A content URI for {@link android.provider.MediaStore} may come from
Android Beam file transfer or from another app, but in both cases you can retrieve a directory
and file name for the content URI.
</p>
<p>
You can also receive an incoming {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}
intent containing a content URI for a content provider other than
{@link android.provider.MediaStore}. In this case, the content URI doesn't contain the
{@link android.provider.MediaStore} authority value, and the content URI usually doesn't point
to a directory.
</p>
<p class="note">
<strong>Note:</strong> For Android Beam file transfer, you receive a content URI in the
{@link android.content.Intent#ACTION_VIEW ACTION_VIEW} intent if the first incoming file
has a MIME type of "audio/*", "image/*", or "video/*", indicating that the file is media-
related. Android Beam file transfer indexes the media files it transfers by running Media
Scanner on the directory where it stores transferred files. Media Scanner writes its results
to the {@link android.provider.MediaStore} content provider, then it passes a content URI
for the first file back to Android Beam file transfer. This content URI is the one you
receive in the notification {@link android.content.Intent}. To get the directory
of the first file, you retrieve it from {@link android.provider.MediaStore} using the content
URI.
</p>
<h3>Determine the content provider</h3>
<p>
To determine if you can retrieve a file directory from the content URI, determine the
the content provider associated with the URI by calling
{@link android.net.Uri#getAuthority Uri.getAuthority()} to get the URI's authority. The
result has two possible values:
</p>
<dl>
<dt>
{@link android.provider.MediaStore#AUTHORITY MediaStore.AUTHORITY}
</dt>
<dd>
The URI is for a file or files tracked by {@link android.provider.MediaStore}. Retrieve the
full file name from {@link android.provider.MediaStore}, and get directory from the file
name.
</dd>
<dt>
Any other authority value
</dt>
<dd>
A content URI from another content provider. Display the data associated with the content
URI, but don't get the file directory.
</dd>
</dl>
<p>
To get the directory for a {@link android.provider.MediaStore} content URI,
run a query that specifies the incoming content URI for the {@link android.net.Uri} argument and
the column {@link android.provider.MediaStore.MediaColumns#DATA MediaColumns.DATA} for the
projection. The returned {@link android.database.Cursor} contains the full path and name for
the file represented by the URI. This path also contains all the other files that Android Beam
file transfer just copied to the device.
</p>
<p>
The following snippet shows you how to test the authority of the content URI and retrieve the
the path and file name for the transferred file:
</p>
<pre>
...
public String handleContentUri(Uri beamUri) {
// Position of the filename in the query Cursor
int filenameIndex;
// File object for the filename
File copiedFile;
// The filename stored in MediaStore
String fileName;
// Test the authority of the URI
if (!TextUtils.equals(beamUri.getAuthority(), MediaStore.AUTHORITY)) {
/*
* Handle content URIs for other content providers
*/
// For a MediaStore content URI
} else {
// Get the column that contains the file name
String[] projection = { MediaStore.MediaColumns.DATA };
Cursor pathCursor =
getContentResolver().query(beamUri, projection,
null, null, null);
// Check for a valid cursor
if (pathCursor != null &amp;&amp;
pathCursor.moveToFirst()) {
// Get the column index in the Cursor
filenameIndex = pathCursor.getColumnIndex(
MediaStore.MediaColumns.DATA);
// Get the full file name including path
fileName = pathCursor.getString(filenameIndex);
// Create a File object for the filename
copiedFile = new File(fileName);
// Return the parent directory of the file
return new File(copiedFile.getParent());
} else {
// The query didn't work; return null
return null;
}
}
}
...
</pre>
<p>
To learn more about retrieving data from a content provider, see the section
<a href="{@docRoot}guide/topics/providers/content-provider-basics.html#SimpleQuery"
>Retrieving Data from the Provider</a>.
</p>

View File

@@ -0,0 +1,294 @@
page.title=Sending Files to Another Device
trainingnavtop=true
@jd:body
<div id="tb-wrapper">
<div id="tb">
<!-- table of contents -->
<h2>This lesson teaches you to</h2>
<ol>
<li><a href="#DeclareFeatures">Declare Features in the Manifest</a>
<li><a href="#TestAndroidBeam">Test for Android Beam File Transfer Support</a></li>
<li>
<a href="#CreateCallback"
>Create a Callback Method That Provides Files</a>
</li>
<li><a href="#ProvideUri">Specify the Files to Send</a>
</ol>
<h2>You should also read</h2>
<ul>
<li><a href="{@docRoot}guide/topics/data/data-storage.html">Storage Options</a></li>
</ul>
</div>
</div>
<p>
This lesson shows you how to design your app to send large files to another device using
Android Beam file transfer. To send files, you request permission to use NFC and external
storage, test to ensure your device supports NFC, and provide URIs to Android Beam file
transfer.
</p>
<p>
The Android Beam file transfer feature has the following requirements:
</p>
<ol>
<li>
Android Beam file transfer for large files is only available in Android 4.1 (API level 16)
and higher.
</li>
<li>
Files you want to transfer must reside in external storage. To learn more about using
external storage, read <a href="{@docRoot}guide/topics/data/data-storage.html#filesExternal"
>Using the External Storage</a>.
</li>
<li>
Each file you want to transfer must be world-readable. You can set this permission by
calling the method {@link java.io.File#setReadable File.setReadable(true,false)}.
</li>
<li>
You must provide a file URI for the files you want to transfer. Android Beam file transfer
is unable to handle content URIs generated by
{@link android.support.v4.content.FileProvider#getUriForFile FileProvider.getUriForFile}.
</li>
</ol>
<h2 id="DeclareFeatures">Declare Features in the Manifest</h2>
<p>
First, edit your app manifest to declare the permissions and features your app needs.
</p>
<h3>Request Permissions</h3>
<p>
To allow your app to use Android Beam file transfer to send files from external storage using
NFC, you must request the following permissions in your app manifest:
</p>
<dl>
<dt>
{@link android.Manifest.permission#NFC NFC}
</dt>
<dd>
Allows your app to send data over NFC. To specify this permission, add the following element
as a child of the <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"
>&lt;manifest&gt;</a></code> element:
<pre>
&lt;uses-permission android:name="android.permission.NFC" /&gt;
</pre>
</dd>
<dt>
{@link android.Manifest.permission#READ_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE}
</dt>
<dd>
Allows your app to read from external storage. To specify this permission, add the following
element as a child of the
<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"
>&lt;manifest&gt;</a></code> element:
<pre>
&lt;uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" /&gt;
</pre>
<p class="note">
<strong>Note:</strong> As of Android 4.2.2 (API level 17), this permission is not
enforced. Future versions of the platform may require it for apps that want to read from
external storage. To ensure forward compatibility, request the permission now, before it
becomes required.
</p>
</dd>
</dl>
<h3>Specify the NFC feature</h3>
<p>
Specify that your app uses NFC, by adding a
<code><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"
>&lt;uses-feature&gt;</a></code> element as a child
of the <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"
>&lt;manifest&gt;</a></code> element. Set the <code>android:required</code> attribute to
<code>true</code> to indicate that your app won't function unless NFC is present.
</p>
<p>
The following snippet shows you how to specify the
<code><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"
>&lt;uses-feature&gt;</a></code> element:
</p>
<pre>
&lt;uses-feature
android:name="android.hardware.nfc"
android:required="true" /&gt;</pre>
<p>
Note that if your app only uses NFC as an option, but still functions if NFC isn't present, you
should set <code>android:required</code> to <code>false</code>, and test for NFC in code.
</p>
<h3>Specify Android Beam file transfer</h3>
<p>
Since Android Beam file transfer is only available in Android 4.1 (API level 16) and later,
if your app depends on Android Beam file transfer for a key part of its functionality you must
specify the <code><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"
>&lt;uses-sdk&gt;</a></code> element with the
<code><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min"
>android:minSdkVersion</a>="16"</code> attribute. Otherwise, you can set
<code><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min"
>android:minSdkVersion</a></code> to another value as necessary, and test for the platform
version in code, as described in the following section.
</p>
<h2 id="TestAndroidBeam">Test for Android Beam File Transfer Support</h2>
<p>
To specify in your app manifest that NFC is optional, you use the following element:
</p>
<pre>
&lt;uses-feature android:name="android.hardware.nfc" android:required="false" /&gt;</pre>
<p>
If you set the attribute
<code><a href="guide/topics/manifest/uses-feature-element.html#required"
>android:required</a>="false"</code>, you must test for NFC support and Android Beam file
transfer support in code.
</p>
<p>
To test for Android Beam file transfer support in code, start by testing that the device
supports NFC by calling {@link android.content.pm.PackageManager#hasSystemFeature
PackageManager.hasSystemFeature()} with the argument
{@link android.content.pm.PackageManager#FEATURE_NFC FEATURE_NFC}. Next, check that the Android
version supports Android Beam file transfer by testing the value of
{@link android.os.Build.VERSION#SDK_INT}. If Android Beam file transfer is supported, get an
instance of the NFC controller, which allows you to communicate with the NFC hardware.
For example:
</p>
<pre>
public class MainActivity extends Activity {
...
NfcAdapter mNfcAdapter;
// Flag to indicate that Android Beam is available
boolean mAndroidBeamAvailable = false;
...
&#64;Override
protected void onCreate(Bundle savedInstanceState) {
...
// NFC isn't available on the device
if (!PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
/*
* Disable NFC features here.
* For example, disable menu items or buttons that activate
* NFC-related features
*/
...
// Android Beam file transfer isn't supported
} else if (Build.VERSION.SDK_INT &lt;
Build.VERSION_CODES.JELLY_BEAN_MR1) {
// If Android Beam isn't available, don't continue.
mAndroidBeamAvailable = false;
/*
* Disable Android Beam file transfer features here.
*/
...
// Android Beam file transfer is available, continue
} else {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
...
}
}
...
}</pre>
<h2 id="CreateCallback">
Create a Callback Method that Provides Files
</h2>
<p>
Once you've verified that the device supports Android Beam file transfer, add a callback
method that the system invokes when Android Beam file transfer detects that the user wants
to send files to another NFC-enabled device. In this callback method, return an array of
{@link android.net.Uri} objects. Android Beam file transfer copies the files represented by
these URIs to the receiving device.
</p>
<p>
To add the callback method, implement the
{@link android.nfc.NfcAdapter.CreateBeamUrisCallback} interface and its method
{@link android.nfc.NfcAdapter.CreateBeamUrisCallback#createBeamUris createBeamUris()}. The
following snippet shows you how to do this:
</p>
<pre>
public class MainActivity extends Activity {
...
// List of URIs to provide to Android Beam
private Uri[] mFileUris = new Uri[10];
...
/**
* Callback that Android Beam file transfer calls to get
* files to share
*/
private class FileUriCallback implements
NfcAdapter.CreateBeamUrisCallback {
public FileUriCallback() {
}
/**
* Create content URIs as needed to share with another device
*/
&#64;Override
public Uri[] createBeamUris(NfcEvent event) {
return mFileUris;
}
}
...
}
</pre>
<p>
Once you've implemented the interface, provide the callback to Android Beam file transfer by
calling {@link android.nfc.NfcAdapter#setBeamPushUrisCallback setBeamPushUrisCallback()}. The
following snippet shows you how to do this:
</p>
<pre>
public class MainActivity extends Activity {
...
// Instance that returns available files from this app
private FileUriCallback mFileUriCallback;
...
&#64;Override
protected void onCreate(Bundle savedInstanceState) {
...
// Android Beam file transfer is available, continue
...
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
/*
* Instantiate a new FileUriCallback to handle requests for
* URIs
*/
mFileUriCallback = new FileUriCallback();
// Set the dynamic callback for URI requests.
mNfcAdapter.setBeamPushUrisCallback(mFileUriCallback,this);
...
}
...
}
</pre>
<p class="note">
<strong>Note:</strong> You can also provide the array of {@link android.net.Uri} objects
directly to the NFC framework through your app's {@link android.nfc.NfcAdapter} instance. Choose
this approach if you can define the URIs to transfer before the NFC touch event occurs.
To learn more about this approach, see {@link android.nfc.NfcAdapter#setBeamPushUris
NfcAdapter.setBeamPushUris()}.
</p>
<h2 id="ProvideUri">Specify the Files to Send</h2>
<p>
To transfer one or more files to another NFC-enabled device, get a file URI (a URI with a
<code>file</code> scheme) for each file and then add the URI to an array of
{@link android.net.Uri} objects. To transfer a file, you must also have permanent read access
for the file. For example, the following snippet shows you how to get a file URI from a file
name and then add the URI to the array:
</p>
<pre>
/*
* Create a list of URIs, get a File,
* and set its permissions
*/
private Uri[] mFileUris = new Uri[10];
String transferFile = "transferimage.jpg";
File extDir = getExternalFilesDir(null);
File requestFile = new File(extDir, transferFile);
requestFile.setReadable(true, false);
// Get a URI for the File and add it to the list of URIs
fileUri = Uri.fromFile(requestFile);
if (fileUri != null) {
mFileUris[0] = fileUri;
} else {
Log.e("My Activity", "No File URI available for file.");
}
</pre>

View File

@@ -423,7 +423,22 @@ include the action bar on devices running Android 2.1 or higher."
</li>
</ul>
</li>
<li class="nav-section">
<div class="nav-section-header">
<a href="<?cs var:toroot ?>training/beam-files/index.html"
description=
"How to transfer files between devices using the NFC Android Beam feature."
>Sharing Files with NFC</a>
</div>
<ul>
<li>
<a href="<?cs var:toroot ?>training/beam-files/send-files.html"
>Sending Files to Another Device</a>
</li>
<li><a href="<?cs var:toroot ?>training/beam-files/receive-files.html"
>Receiving Files from Another Device</a></li>
</ul>
</li>
<li class="nav-section">
<div class="nav-section-header">
<a href="<?cs var:toroot ?>training/basics/network-ops/index.html"
@@ -498,7 +513,7 @@ include the action bar on devices running Android 2.1 or higher."
"How to design a robust conflict resolution strategy for apps that save data to the cloud."
>Resolving Cloud Save Conflicts
</a>
</li>
</li>
</li>
<li class="nav-section">
<div class="nav-section-header">
@@ -1181,7 +1196,6 @@ include the action bar on devices running Android 2.1 or higher."
</a>
</div>
<ul>
<li>
<a href="<?cs var:toroot ?>training/articles/security-tips.html"
description=