Consult the documentation that came with your phone for further instructions.
@@ -50,7 +28,7 @@ page.tags="dialog","toast","notification"
- Better
+ Do
Read the instructions that came with your phone.
@@ -59,27 +37,27 @@ page.tags="dialog","toast","notification"
-
-
-- Keep it simple. From the Location settings screen:
+Don't provide unnecessary information
- Confusing
+ From a Setup Wizard screen
- Use GPS satellites
+ Signing in...
|
- When locating, accurate to street level.
+ Your phone needs to communicate with
+ Google servers to sign in to your account.
+ This may take up to five minutes.
|
@@ -88,20 +66,21 @@ page.tags="dialog","toast","notification"
- Better
+ From a Setup Wizard screen
- GPS
+ Signing in...
|
- Let apps use satellites to pinpoint your location.
+ Your phone is contacting Google.
+ This can take up to 5 minutes.
|
@@ -110,143 +89,111 @@ page.tags="dialog","toast","notification"
-
+Simple
+
+
+ - Use short words, active verbs, and common nouns.
+ - Put the most important thing first. “Front-load” the first 11 characters
+ with the most salient information in the string.
+ - Don’t try to explain subtle differences. They are lost on most users.
+
-- Be friendly. Dialog that appears when an application
-crashes:
+Focus on the user's concern, not technical details
- Confusing and annoying—"Sorry" just rubs salt in the
- wound.
+ Don't
-
-
-
-
- Sorry!
- |
-
-
-
-
-
- Activity MyAppActivity (in application MyApp)
- is not responding.
- |
-
-
-
-
- Force close |
- Wait |
- Report |
-
-
-
+
+ Manually control GPS to prevent other apps from using it
+ |
- Shorter, more direct, no faux-apologetic title:
+ Do
-
-
-
-
- MyApp isn't responding.
- |
-
-
-
-
-
- Do you want to close it?
- |
-
-
-
-
- Wait |
- Report |
- Close |
-
-
-
+
+ To save power, switch Location mode to Battery saving
+ |
-
-
-- Put the most important thing first.
+Put top news first
- Top news last
+ Don't
-
- 77 other people +1'd this, including Larry Page.
+
+ 77 other people +1’d this, including Larry Page
|
- Top news first
+ Do
- Larry Page and 77 others +1'd this.
+ Larry Page and 76 others +1’d this
|
+Put the user's goal first
+
- Task last
+ Don't
-
- Touch Next to complete setup using a Wi-Fi connection.
+
+ Touch Next to complete setup using a Wi-Fi connection
|
- Task first
+ Do
- To finish setup using Wi-Fi, touch Next.
+ To finish setup using Wi-Fi, touch Next
|
-
-- Describe only what's necessary, and no more.
+Friendly
+
+ - Use contractions.
+ - Talk directly to the reader. Use “you” to refer to the reader.
+ - Keep your tone casual and conversational, but avoid slang.
+
+
+
+Avoid being confusing or annoying
-
- From a Setup Wizard screen
-
+ Don't
- Signing in...
+ Sorry!
|
- Your phone needs to communicate with
- Google servers to sign in to your account.
- This may take up to five minutes.
+ Activity MyAppActivity (in application
+ MyApp) is not responding
|
@@ -254,26 +201,122 @@ crashes:
-
- From a Setup Wizard screen
-
+ Do
- Signing in...
+ MyApp isn’t responding
|
- Your phone is contacting Google.
- This can take up to 5 minutes.
+ Do you want to close it?
|
-
+
+
+Words to avoid
+
+
+
+
+ Don't use |
+ Use |
+
+
+ one, two, three, four, ... |
+ 1, 2, 3, 4, ... |
+
+
+ application |
+ app |
+
+
+ cannot, could not, do not, did not
+will not, you will |
+ Contractions: can’t, couldn’t, don’t, didn’t won’t, you’ll, and so on |
+
+
+ okay, ok |
+ OK |
+
+
+ please, sorry, thank you |
+ Attempts at politeness can annoy the user, especially in messages that say
+ something has gone wrong.
+ Exception: In Japanese, “please” is mandatory and imperative verbs should
+ be localized accordingly (turn on -> please turn on).
+ |
+
+
+ there is, there are, it is
+ and other “disappeared” subjects (grammatical expletives) |
+ Use a noun as the subject |
+
+
+ abort, kill, terminate |
+ stop, cancel, end, exit |
+
+
+ fail, failed, negative language |
+ In general, use positive phrasing
+ (for example, “do” rather than “don’t,” except in cases such as “Don’t show
+ again,” “Can’t connect,” and so on.) |
+
+
+ me, I, my, mine |
+ you, your, yours |
+
+
+ Are you sure? Warning! |
+ Tell user the consequence instead, for example, “You’ll lose all photos
+ and media” |
+
+
+
+
+
+Formatting text
+
+Capitalization
+
+
+ - Use sentence-style capitalization for all UI strings: “Words to live by.”
+ - Capitalize all important words in:
+
+ - App names (Calendar, Google Drive)
+ - Named features (Android Beam, Face Unlock)
+ - Proper nouns (Statue of Liberty, San Francisco Giants)
+
+
+ - Be conservative. Don't capitalize words that aren't part of a formal feature name:
+
+ - Sim card lock, Home screen, not Sim Card Lock, Home Screen.
+
+
+
+
+
+Punctuation
+
+ - Period. Don't use a period after a single sentence or
+ phrase used in isolation, such as in a toast, label, or notification. Wherever two or
+ more sentences run together, use a period for each sentence.
+ - Ellipsis. Use the ellipsis character (…) (Option-; on MacOS and …
+ in HTML) to indicate
+
+ - Incompleteness, such as an action in progress (“Downloading...”) or truncated text.
+ - That a menu item (such as Print… or Share…) leads to further UI involving significant
+ choices. Exception: Commands whose wording already implies further (but limited) UI, such
+ as Find in page or Pick a date, do not require an
+ ellipsis.
+
+
+
\ No newline at end of file
diff --git a/docs/html/design/videos/index.jd b/docs/html/design/videos/index.jd
index 91a784a0b31ea9a3ab878950c8bba5450999933a..976767d263d4f34d32fbf1d3bc4c80a08ff17cfc 100644
--- a/docs/html/design/videos/index.jd
+++ b/docs/html/design/videos/index.jd
@@ -46,7 +46,7 @@ page.title=Videos
- In the Android UX team, it is critical to get user feedback frequently and consistently so that we are able to iterate and develop the best-in-class designs for our users. We will discuss how the team applied ""Pulse Studies"" (iterative research sessions) in order to put new ideas, designs, and concepts in front of users on a regular basis; it requires minimal advance planning, it can have an immediate product impact, and it can meet urgent needs.
+ In the Android UX team, it is critical to get user feedback frequently and consistently so that we are able to iterate and develop the best-in-class designs for our users. We will discuss how the team applied "Pulse Studies" (iterative research sessions) in order to put new ideas, designs, and concepts in front of users on a regular basis; it requires minimal advance planning, it can have an immediate product impact, and it can meet urgent needs.
diff --git a/docs/html/develop/index.jd b/docs/html/develop/index.jd
index 1833f24e2e7565488990fa576c3cc633d6dc0594..61a98b7ad8a827a95f2772bdf1b9b1383a892137 100644
--- a/docs/html/develop/index.jd
+++ b/docs/html/develop/index.jd
@@ -96,18 +96,6 @@ class="play no-shadow no-transform" style="margin:0 0 0 40px;max-height:250px;he
href="{@docRoot}google/play-services/maps.html" class="button">Read more
-
-
-
-
- In-app Subscriptions with Trials
- You can now set up a free trial period for any Google Play in-app subscription, making it easy for users try your subscriber content before automatically converting to a full subscription. Free trials give you a new way to bring users into your products and engage them effectively.
- Read
-more
-
-
@@ -134,15 +122,15 @@ more
The Beautiful Design Summer 2013 Collection
See the apps chosen by the Android Design team for their masterfully crafted design details...
-
-
- Google Play Developer 8-Step Checkup
- Give your Google Play developer account this quick checkup to keep it in good order and help you deliver a more successful product to users...
+
+
+ New Features in Google Play Games
+ Three new features that make it easier to understand what players are doing in your game and help you manage game features...
-
- New Ways to Optimize Your Business in Google Play
- Many of you have invested in making great tablet experiences for your users, and we want to ensure that that work pays off...
+
+ Linking Google Analytics with Google Play
+ Understanding your users easier through a new integration between Google Analytics and the Google Play Developer Console...
diff --git a/docs/html/distribute/distribute_toc.cs b/docs/html/distribute/distribute_toc.cs
index ecdf2a8423017acd818eb4bf8aa0e3baf672a496..1fabcb301fd23dbf63b2d61817b6deb8e040a48b 100644
--- a/docs/html/distribute/distribute_toc.cs
+++ b/docs/html/distribute/distribute_toc.cs
@@ -81,6 +81,7 @@
@@ -90,6 +91,7 @@
About
Get Started
Guidelines
+ FAQ
Sign Up
diff --git a/docs/html/distribute/googleplay/edu/about.jd b/docs/html/distribute/googleplay/edu/about.jd
index cc131c648d2e8e1a186b57c9185892fcee13d7e5..20a0d4d782e420c650f6b766918deb1751188014 100644
--- a/docs/html/distribute/googleplay/edu/about.jd
+++ b/docs/html/distribute/googleplay/edu/about.jd
@@ -3,26 +3,25 @@ page.metaDescription=How Google Play for Education helps you reach a new audienc
excludeFromSuggestions=true
@jd:body
-
-
+
Introducing Google Play for Education, the online destination where schools
can find the right tablet content and tools for their students and teachers.
-With easy bulk ordering for groups, schools will be able to purchase and
-instantly distribute apps, videos, and books right to their students’
+ With easy bulk ordering for groups, schools can purchase and
+instantly distribute your apps, and videos right to their students’
devices.
-The Google Play team looks forward to seeing you create first class content
-that will help schools. We want to help you create innovative educational apps,
-without having to knock on school doors to reach teachers and students.
+Google Play for Education can help your innovative educational apps
+gain visibility with the right audiences, without having to knock on school doors.
Watch a Video
@@ -32,36 +31,36 @@ without having to knock on school doors to reach teachers and students.
Get discovered
-With Google Play for Education, teachers and administrators will be able to
+ With Google Play for Education, teachers and administrators can
browse content by curriculum, grade, and standard — discovering the right
-content at the right time for their students. If your app offers an exciting new
-way to learn sixth grade algebra, we'll make it easy for math educators to find,
-purchase, and distribute your app to their classes.
+content for their students. If your app offers an exciting new
+way to learn sixth grade algebra, math educators will be able to find,
+purchase, and distribute your app to their classes in a few clicks.
Reach more schools and students
-Google has built a strong network of K-12 schools who are already using
-Google Apps for Education and other Google services. These schools are excited
-and looking forward to bringing your apps and content into their classrooms with
-Nexus tablets.
+Over 30 million students, faculty, and staff are already using
+Google Apps for Education and other Google services. Many of these schools are
+excited to take advantage of tablets with Google Play for Education and they
+look to bringing your apps into their classrooms,
+especially apps using Google sign-on.
Monetize effectively
-With the wide launch of Google Play for Education later this year, educators
-will be able to make high-volume purchases using standard institutional payment
-mechanisms and distribute them to the students they want — whether it is a
-class of 30 or a district of 30,000.
-
+With Google Play for Education, educators are able to make high-volume purchases
+using standard institutional payment mechanisms and distribute them to the students
+they want — whether it is a class of 20 or a district of 20,000.
+
For Educators
Android tablets in the classroom
Google Play for Education brings the innovation of Android technology
-into classrooms. Educators can set up and deploy large numbers of devices in
+into classrooms. School districts can set up and deploy large numbers of devices in
just minutes or hours rather than days.
Curriculum-based discovery
- Powerful browsing tools let educators quickly discover apps, books,
+ Powerful browsing tools let educators quickly discover apps,
videos, and other content—with many recommended by teachers and
categorized according to familiar Core Curriculum standards.
diff --git a/docs/html/distribute/googleplay/edu/contact.jd b/docs/html/distribute/googleplay/edu/contact.jd
index 804d925864cb5b1238930b37ab4e31987f65d424..ca8343842881edb25f8d32839b65a28d5addef89 100644
--- a/docs/html/distribute/googleplay/edu/contact.jd
+++ b/docs/html/distribute/googleplay/edu/contact.jd
@@ -5,11 +5,7 @@ excludeFromSuggestions=true
We're looking forward to improving how students learn in the classroom as we
bring your first-class educational content into schools across the United
-States, and to a broader international audience in the future. We'll soon share
-more information about Google Play for Education and our services that will help
-teachers and administrators buy, deploy, and use apps.
-
-
+States, and to a broader international audience in the future.
@@ -35,8 +31,8 @@ educational apps.
If you're a school or system interested in tablets and Google Play for Education,
complete the expression of interest form at www.google.com/edu/android.
-We'll be in touch later in the year as the program launches widely to schools.
School Interest Form »
+
diff --git a/docs/html/distribute/googleplay/edu/faq.jd b/docs/html/distribute/googleplay/edu/faq.jd
new file mode 100644
index 0000000000000000000000000000000000000000..6afc1073615d03e40b106d95405700ca4d3f4945
--- /dev/null
+++ b/docs/html/distribute/googleplay/edu/faq.jd
@@ -0,0 +1,372 @@
+page.title=Google Play for Education FAQ
+page.metaDescription=Questions and answers about Google Play for Education.
+excludeFromSuggestions=true
+@jd:body
+
+
+
+
+
+
+
+
+
+ The sections below provide more information about Google Play for Education
+ and answer common questions that you might have about it.
+
+
+
+Business Model and Monetization
+
+
+ -
+ What is Google Play for Education?
+
+
+ -
+ Google Play for Education is a new online destination designed for schools.
+ Teachers can discover educational apps, books, and videos to meet the needs
+ of a single student, a classroom, or a whole district. Educators can browse
+ apps by grade, subject, keyword, or standard including common core.
+ Purchasing is done via PO with no credit card required. Apps are
+ distributed to tablets instantly via the cloud.
+
+
+ -
+ Is Google Play for Education primarily for students or educators?
+
+
+ -
+ The store on Google Play for Education is for educators, but its content is
+ for both educators and students. Teachers and administrators have the
+ ability to make purchases and control who within their school has access to
+ the purchase flows.
+
+
+ -
+ Will Google Play for Education support subscription purchases?
+
+
+ -
+ Currently, Google Play for Education supports one-time purchases. We are
+ investigating additional purchase mechanisms to enable more flexible
+ pricing models for developers and schools.
+
+
+ -
+ Why is it recommended to disable in-app purchases?
+
+
+ -
+ In-app purchase is currently not supported with Google Play for Education,
+ and a student device will block the Play transaction if a student attempts
+ to make an in-app purchase. To avoid student confusion in the classroom,
+ also recommend not including any in-app purchase buttons and other UI in
+ your application. We are investigating additional purchase mechanisms to
+ enable more flexible pricing models for developers and schools.
+
+
+ -
+ Is Google Play for Education restricted so only its users can purchase from
+ the Google Play for Education? Or will anyone be able to purchase from it?
+
+
+ -
+ Currently, only schools that are signed up for Google Play for Education
+ can make purchases on it.
+
+
+ -
+ Is there a way to differentiate an app's pricing between Google Play for
+ Education and Google Play?
+
+
+ -
+ For each app that you publish, you can set a single price that applies to
+ both Google Play and Google Play for Education &mdash. You can’t set a
+ different price for a given app (based on a single package name) in Google
+ Play for Education.
+
+
+
+
+Free Trials
+
+
+ -
+ Can I offer free trials through Google Play for Education?
+
+
+ -
+ Google Play for Education doesn't currently support free trials. If you
+ want, you can offer a free version of your app with limited functionality
+ in Google Play for Education, but that app would need to be separate from
+ your paid app and be reviewed separately for educational content.
+
+
+ -
+ Can I offer a free trial through Google Play's "In-app Subscriptions with
+ Free Trials" feature?
+
+
+ -
+ Google Play for Education does not currently support In-app Billing or
+ In-app Subscriptions with free trials.
+
+
+
+
+Discovery
+
+
+ -
+ What are the categories in Google Play for Education?
+
+
+ -
+ Google Play for Education includes categories for all grade levels from
+ Kindergarten to 12 and the following subjects: English Language Arts, World
+ Languages, Mathematics, Science, Social Science, Elective, OER (Open
+ Education Resources), and Tools.
+
+
+ -
+ I created an app specifically for Google Play for Education and do not want
+ it to show up in Google Play. Is this possible?
+
+
+ -
+ Currently, it is not possible to publish an app Google Play for Education
+ and make it unavailable on Google Play.
+
+
+ -
+ If my app offers content for every level of education, how will it fit the
+ common-core standard filters?
+
+
+ -
+ If your app applies to multiple levels of education, then the app will show
+ up filtered results for in multiple levels.
+
+
+
+
+App Review Process
+
+
+ -
+ How are apps being reviewed? By whom and with what criteria?
+
+
+ -
+ Apps are being reviewed by a third party network of educators. These
+ educators assign the appropriate subject, grade, and common core standards
+ metadata, as well as evaluating whether the app meets the Google Play for
+ Education criteria for
+ classroom use. You can learn more about the submission process and
+ criteria at developer.android.com/edu.
+
+
+ -
+ How do I update my apps in Google Play for Education?
+
+
+ -
+ Developers can update their apps on Google Play for Education in the same
+ manner that they do for Google Play. App updates will not be reviewed prior
+ to being made available through Play for Education. However, we will
+ periodically review updated apps for quality.
+
+
+ -
+ Does the app maturity rating reflect solely what a user can do within my
+ Android app, or does the web version of my app influence the rating as
+ well?
+
+
+ -
+ The maturity rating that you set for your Android app refers only to the
+ content displayed in that application.
+
+
+
+
+App Features
+
+
+ -
+ Do I need separate builds of my phone and tablet apps for Google Play for
+ Education, or is it the exact same app that lives on Google Play?
+
+
+ -
+ We recommend you create one app and use it in both Google Play and Google
+ Play for Education.
+
+
+ -
+ What is the best way to get students’ work within apps sent back to their
+ teachers?
+
+
+ -
+ Many teachers have mentioned that the way apps treat this now is via an
+ email from a third party, which is not optimal for schools. As many schools
+ use Google Apps for Education, consider integrating your app with Google
+ Drive using the SDK which can be found here: developers.google.com/drive/about-sdk.
+
+
+ -
+ How can developers test the teacher experience in Google Play for
+ Education? Is there a way to get an account to test it?
+
+
+ -
+ Currently, we are unable to provide developers with a test account to test
+ the Google Play for Education user experience. We are investigating ways to
+ allow developers to simulate the environment.
+
+
+ -
+ If I already have an app in the Chrome Apps Pack will I get some help
+ migrating this to Android?
+
+
+ -
+ If you’d like to reach users of Nexus tablets for schools we encourage you
+ to build a native app for the optimal user experience. Considerations for
+ building your app and instructions for registering it can be found at
+ developer.android.com/edu.
+
+
+
+
+Marketing and ROI
+
+
+ -
+ What are you doing to promote these apps to educators?
+
+
+ -
+ Google Play for Education is an extension of Google Play targeting schools
+ and making discovery easier for educational apps. It helps your apps gain
+ visibility with the right audiences, without having to knock on school
+ doors. We are constantly referring to the highest quality apps in our
+ educator outreach. We have also developed a series of collections to help
+ educators quickly browse apps for the most common use cases.
+
+
+ -
+ How many installs have similar apps had on Play? How much can I expect to
+ make if I do an ROI analysis?
+
+
+ -
+ While we cannot disclose specific numbers, Google Play app listings provide
+ app download ranges for all apps.
+
+
+ -
+ What is the seasonality like for the education market? What are the key
+ timing considerations for app developers?
+
+
+ -
+ In the United States, school districts’ budget decisions go through a
+ planning phase in the Spring with budgets being released on July 1. We have
+ observed high purchase-volumes in the second quarter of the calendar year,
+ using up end-of-year budgets. New budget purchases begin in the third
+ quarter of the calendar year.
+
+
+ -
+ Is there a way to offer a special deal, such as a discount, only on Google
+ Play for Education and not on Google Play?
+
+
+ -
+ No, this is not possible. Pricing, including special offers, must be the
+ same between Google Play for Education and Google Play.
+
+
+
+
+Devices
+
+
+ -
+ Which devices are available in the program? Will more be available?
+
+
+ -
+ Nexus 7 is available for shipment now, and the Asus Transformer will be
+ available in early 2014. We look forward to welcoming more Android devices
+ into the Google in Education family soon.
+
+
+ -
+ Can the devices be shared among many students?
+
+
+ -
+ No. Currently, this program is for one-to-one usage. Each student can login
+ to one specific tablet that is allocated to them.
+
+
+
+
+
+ Accounts
+
+
+
+ -
+ Will an app know whether a user is a teacher or student?
+
+
+ -
+ No, the app has no mechanism for knowing if it is running on a teacher’s
+ device or a student’s device. We recommend developers use their own user
+ database to enable this feature, where logins can be based on Google
+ Account information.
+
+
+ -
+ What log-in method do you recommend for an app on Google Play for
+ Education?
+
+
+ -
+ One of the key pieces of feedback we have heard multiple times from various
+ schools is that they prefer apps that offer Google Single Sign-on, so that
+ teachers and students do not need to remember multiple log-in credentials.
+ As schools in the program use Google Accounts and Google Apps for
+ Education, offering Google Single Sign-on is ideal.
+
+
\ No newline at end of file
diff --git a/docs/html/distribute/googleplay/edu/guidelines.jd b/docs/html/distribute/googleplay/edu/guidelines.jd
index c1d30651db0127a12739ddf9239ca5b534f1591b..c4b719bf9c4a1e10655639c6a83dffd9454b11f4 100644
--- a/docs/html/distribute/googleplay/edu/guidelines.jd
+++ b/docs/html/distribute/googleplay/edu/guidelines.jd
@@ -3,18 +3,16 @@ page.metaDescription=Get your apps ready for Google Play for Education.
excludeFromSuggestions=true
@jd:body
-
-You
-can now include your apps in the Google Play for Education pilot program,
-getting it into the hands of participating schools and key influencers in the
-education technology community. See Get Started to
+ You
+can now include your educational apps in the recently launched Google Play for Education program,
+getting it into the hands of participating schools and key influencers in the education technology
+community. See Get Started to
learn how to participate.
The sections below list the guidelines and requirements for apps
@@ -28,8 +26,8 @@ you develop a great app for students that offers compelling content and an
intuitive user experience on Android tablets.
In addition, ensure that your app complies with the terms of a Google Play for Education Addendum, as well as
+href="https://play.google.com/about/developer-distribution-agreement-addendum.html"
+target="_policies">Google Play for Education Addendum, as well as
the standard Google Play Developer Program Policies and
- Android version — Test the app on devices running Android
4.2. Google Play for Education devices will be running Android 4.2 or higher
-(API level 17).
+(API level 17+).
- Proxy server — Test the app in network environment that uses
proxies. Many schools use proxies.
-- Secondary user account — Test the app using a secondary user
-account. Most Google Play for Education users will not be using the primary multiuser
-account on their devices. For testing, create a secondary multiuser account on
-your tablet.
- No location services — Test the app to make sure it works
properly with location services disabled. Many schools will disable location
services for student devices.
@@ -249,4 +242,3 @@ devices.
- No access to network — Test the app to make sure it works
properly when the device cannot connect to the internet.
-
diff --git a/docs/html/distribute/googleplay/edu/index.jd b/docs/html/distribute/googleplay/edu/index.jd
index de5fe3513a84a39fd4898a7b47f745320c419090..7a16bfdaea0db4d007c9c84d249cd8da4051d245 100644
--- a/docs/html/distribute/googleplay/edu/index.jd
+++ b/docs/html/distribute/googleplay/edu/index.jd
@@ -10,20 +10,21 @@ header.hide=1
style="display: block;text-align: right;">SIGN UP
-
-
+
+
-
+
Google Play for Education
- A destination where schools can find great educational content in Google Play.
- Bulk purchase and instant distribution let educators bring your apps directly
- to classrooms and schools.
- Read More
+ Google Play for Education is a destination where schools can find great,
+ teacher-approved, educational apps and videos on Play Store. Teachers can filter
+ content by subject matter, grade and other criteria. Bulk purchase and instant
+ distribution let educators bring your apps directly to classrooms and schools.
+ If you have an educational app, be a part of Google Play for Education.
+ Learn More
-
+
+
diff --git a/docs/html/distribute/googleplay/edu/start.jd b/docs/html/distribute/googleplay/edu/start.jd
index 78b87396bdd44f31187840c960a8a6a9dc7d87cb..01d44068c1543a730c344cecec0a8fac6339af93 100644
--- a/docs/html/distribute/googleplay/edu/start.jd
+++ b/docs/html/distribute/googleplay/edu/start.jd
@@ -3,18 +3,19 @@ page.metaDescription=Get Started with Google Play for Education
excludeFromSuggestions=true
@jd:body
-
+
You
-can now include your apps in the Google Play for Education pilot program,
-getting it into the hands of participating schools and key influencers in the education technology
-community. See the sections below to learn more.
+can now include your educational apps in the Google Play for Education program,
+getting it into the hands of participating schools and key influencers in the
+education technology community. See the sections below to learn more.
- If you've got a great app for education or just an idea for one, plan to be a
+ If you've got a great app for education, be
part of Google Play for Education to reach even more teachers and students. It's
easy to participate, and you'll be able to offer new or existing Android apps
using familiar tools and processes in Google Play.
@@ -26,8 +27,8 @@ Apps for information on the safety, usability, and quality standards that
your apps should meet. When your app is ready, you can opt-in to Google Play for
Education from the Developer Console.
- Note that the initial launch of Google Play for Education is planned for Fall
-2013 and will include schools in the United States only, with support for other
+ Note that Google Play for Education is currently available to schools in the
+United States only, with support for schools in other
countries to follow. At this time, please include your app in Google Play for
Education only if it is targeting the US K-12 market.
@@ -35,11 +36,12 @@ Education only if it is targeting the US K-12 market.
How to Participate
+src="{@docRoot}images/gp-edu-process.png" />
- Google Play for Education lets you put your educational apps in front of a
+ Google Play for Education is a great way to put your educational apps in front of a
new audience of teachers and students. You can develop and publish using
-familiar tools and processes, such as your existing Developer Console account
+familiar tools and processes, such as your existing Developer Console account
and your current distribution and pricing settings. It's easy to participate
— the sections below outline the process.
@@ -109,7 +111,7 @@ modifications to your app.
-When you've built your release-ready APK and tested to ensure that it meets
+ Once you've built your release-ready APK and tested to ensure that it meets
the app guidelines,
upload it to the Developer Console, create your store listing, and set
distribution options. If you aren't familiar with how to prepare for launch on
@@ -117,7 +119,8 @@ Google Play, see the Launch Checklist.
When your app is ready to publish, you can opt-in to Google Play for
-Education from the Developer Console. Opt-in means that you want your app to be
+Education directly from the Developer Console. Opt-in means that you want your app to be
made available to educators through Google Play for Education, including review,
classification, and approval by our third-party educator network. Note that
opt-in does not affect the availability of your app in Google Play Store.
@@ -141,18 +144,21 @@ Addendum, make sure to read them before opting-in.
opt-in.
Under Pricing and Distribution, scroll down to find "Google Play for
Education" and the opt-in checkbox.
- Click the checkbox next to "Include my app in Google Play for
-Education..."
- After you've opted-in, find the "Ads" and "In-app purchases" checkboxes below.
-Check each checkbox that applies. Your app's use of ads or in-app purchases will
+ Click the checkbox next to "Include this application in Google Play for
+Education."
+ In the first dialog that appears, review the content policies and guidelines
+ and click "Continue" if your app meets the the policies and guidelines.
+ In next dialog that appears, shown below, find the "Ads" and "In-app purchases" radio
+ buttons. Check each option that applies. Your app's use of ads or in-app purchases will
be shown to educators when they are browsing your app.
Click "Save" to save your Pricing and Distribution changes.
-
- Opt-in for apps:
-Include your app in Google Play for Education by opting-in from the Developer Console.
+
+ Ads and in-app purchase:
+When you opt-in to Google Play for Education, make sure to declare your app's use of ads and
+in-app purchases.
Once you save changes and publish your app, the app will be submitted to our
@@ -176,23 +182,20 @@ them discoverable through the Google Play for Education browsing tools.
Our third-party educator network will evaluate apps according to educational
value and alignment with K-12 core standards, then assign the metadata for
-subject, grade level, and core curriculum that makes them easily browseable for
+subject, grade level, and core curriculum that makes them easily browsable for
educators. To understand how your apps will be evaluated, please see the Guidelines for
Apps document.
As soon as you opt-in to Google Play for Education and publish, your app is
queued for review by our third-party educator network. The review and approval
-process can take 3-4 weeks or more. You'll receive notification
+process can take four weeks or more. You'll receive notification
by email (to your developer account address) when the review is complete, with a
summary of the review results.
-Note: Until the full product launch in Fall
-2013, please expect the initial review of your app to take longer than usual.
-
-
At any time, you can check the review and approval status of your app in the
-Developer Console, under "Google Play for Education" in the app's Pricing and
+Developer Console, under
+"Google Play for Education" in the app's Pricing and
Distribution page. There are three approval states:
@@ -200,9 +203,6 @@ Distribution page. There are three approval states:
is not yet complete.
- Approved — Your app was reviewed and approved. The app
will be made available directly to educators through Google Play for Education.
-Until the full product launch later this year, your app will be available to a
-limited number of educators through the pilot program.
Once your app is approved, you can update it at your convenience without needing
another full review.
- Not approved — Your app was reviewed and not approved.
@@ -215,50 +215,14 @@ discussed in the next section.
5. Get support or appeal your review results
-After your app is reviewed you'll receive an email giving you the review
-results, including whether the app was approved, how the app was classified, and
+ After your app is reviewed you'll receive an email giving you the
+results, including information on whether the app was approved and
what issues may need to be addressed. You'll receive the email at the address
you specified for your developer account.
-If you believe your app was reviewed or classified incorrectly, you will be
-able to appeal and request reconsideration. Watch for more information on the
-appeal process and links in the weeks to come.
-
-Note: Support and appeal forms are not yet
-available, but will be available soon.
-
-
-Including Your Apps in the Pilot Program
-
-Leading up to the Fall 2013 launch, the Google Play for Education team is
-conducting an extensive series of pilots that include schools and students across
-the United States. Educators in participating schools can browse for apps and
-purchase them in bulk, then deploy them instantly to teacher and student
-devices.
-
-Early opt-in and publishing
-As an app developer, you can take part in the pilot program, getting your app
-into the hands of schools and key influencers in the education technology
-community. It's a great way to get early feedback on your educational app.
-
-To offer your app in the pilot program, prepare the app and ensure that it meets
-the Guidelines
-for Apps. Then opt-in to Google Play for Education and publish as soon
-as you are ready. Once your app is approved during review by our third-party
-educator network, it will be made available to educators in the pilot program
-right away. Note that during the pilot program, the review and approval process
-may take longer than usual.
-
-Full launch to US schools
-The initial launch of Google Play for Education is planned for Fall 2013. The
-pilot program and full launch will include schools in the United States only,
-with support for schools in other countries to follow.
-
-At this time, you should include your app in Google Play for Education only
-if it is targeting the US K-12 market.
-
-More information
+If your app has issues that need to be addressed, make the necessary
+adjustments, upload your app, and then resubmit the app to Google Play for
+Education through the Developer Console using process described above. Your app
+will be queued for review and you'll receive the review results by email just
+as before.
-If you'd like to be notified by email of the latest information about Google Play
-for Education, visit the
-Sign Up page and fill out the form.
\ No newline at end of file
diff --git a/docs/html/distribute/googleplay/publish/localizing.jd b/docs/html/distribute/googleplay/publish/localizing.jd
index 29b27c866ac0150a3584bd004c0db95c6dcee3ad..1a5f3c1c5b2843b87a4c80b85ed935d3ff9d90b0 100644
--- a/docs/html/distribute/googleplay/publish/localizing.jd
+++ b/docs/html/distribute/googleplay/publish/localizing.jd
@@ -293,7 +293,7 @@ usage.
Related resources:
@@ -372,24 +372,24 @@ properly.
After the translations are merged back into your app, start testing the localized app.
+Purchase professional translations through Google Play
+ App Translation Service
+
+ADT Translation Manager Plugin.
-Purchase professional translations through the
-Developer Console
-
-Google Play can help you quickly find and purchase translations of your app.
+ Google Play App Translation Service can help you quickly find and purchase translations of your app.
In the Developer Console, you can browse a list of third-party vendors who are
pre-qualified by Google to offer high-quality translation at competitive prices.
You can upload the strings you want translated, select the languages you want to
diff --git a/docs/html/distribute/googleplay/publish/preparing.jd b/docs/html/distribute/googleplay/publish/preparing.jd
index 5593f4f8efc97382c0543927a2742f04a6690747..b9dd0e067771e099f365b1605fd2a9f5abebfc2a 100644
--- a/docs/html/distribute/googleplay/publish/preparing.jd
+++ b/docs/html/distribute/googleplay/publish/preparing.jd
@@ -671,7 +671,7 @@ appreciate that you are serious about improving the quality of your app.
- Supporting your users
— Help Center document describing options for supporting users.
- In-app Billing — Help Center document describing how to correctly set up In-app Billing.
-- Issuing Refunds — -- Help Center document describing how to issue refunds.
+- Issuing Refunds — -- Help Center document describing how to issue refunds.
|
diff --git a/docs/html/distribute/googleplay/spotlight/index.jd b/docs/html/distribute/googleplay/spotlight/index.jd
index c59962892caee4d12be0bd9e2ddc0651c82bd3f6..b501f2059a37aeda6719fb87d8ab4cc3d49265de 100644
--- a/docs/html/distribute/googleplay/spotlight/index.jd
+++ b/docs/html/distribute/googleplay/spotlight/index.jd
@@ -55,7 +55,7 @@ header.hide=0
- Bangalore-based developers redBus.in are bringing the sophistication and convenience of air-travel booking to bus transit. Hear how Android is helping them deliver a superior travel experience to millions of daily bus riders in India.
+ Bangalore-based developers redBus.in are bringing the sophistication and convenience of air-travel booking to bus transit. Hear how Android is helping them deliver a superior travel experience to millions of daily bus riders in India.
- Automatic Retry Using Exponential Back-Off
-- How Unregistration Works
+- Unregistration
+
+ - Why you should rarely unregister
+ - How unregistration works
+
+
- Send-to-Sync vs. Messages with Payload
- Send-to-sync messages
@@ -31,7 +36,8 @@ page.title=GCM Advanced Topics
- Setting an Expiration Date for a Message
-- Receiving Messages from Multiple Senders
+- Receiving Messages from
+Multiple Senders
@@ -42,17 +48,56 @@ page.title=GCM Advanced Topics
Lifetime of a Message
-When a 3rd-party server posts a message to GCM and receives a message ID back, it does not mean that the message was already delivered to the device. Rather, it means that it was accepted for delivery. What happens to the message after it is accepted depends on many factors.
-In the best-case scenario, if the device is connected to GCM, the screen is on, and there are no throttling restrictions (see Throttling), the message will be delivered right away.
-If the device is connected but idle, the message will still be
-delivered right away unless the delay_while_idle flag is set to true. Otherwise, it will be stored in the GCM servers until the device is awake. And that's where the collapse_key flag plays a role: if there is already a message with the same collapse key (and registration ID) stored and waiting for delivery, the old message will be discarded and the new message will take its place (that is, the old message will be collapsed by the new one). However, if the collapse key is not set, both the new and old messages are stored for future delivery.
+When a 3rd-party server posts a message to GCM and receives a message ID back,
+it does not mean that the message was already delivered to the device. Rather, it
+means that it was accepted for delivery. What happens to the message after it is
+accepted depends on many factors.
-Note: There is a limit on how many messages can be stored without collapsing. That limit is currently 100. If the limit is reached, all stored messages are discarded. Then when the device is back online, it receives a special message indicating that the limit was reached. The application can then handle the situation properly, typically by requesting a full sync.
+In the best-case scenario, if the device is connected to GCM, the screen is on,
+and there are no throttling restrictions (see Throttling),
+the message will be delivered right away.
-If the device is not connected to GCM, the message will be stored until a connection is established (again respecting the collapse key rules). When a connection is established, GCM will deliver all pending messages to the device, regardless of the delay_while_idle flag. If the device never gets connected again (for instance, if it was factory reset), the message will eventually time out and be discarded from GCM storage. The default timeout is 4 weeks, unless the time_to_live flag is set.
-
-Finally, when GCM attempts to deliver a message to the device and the application was uninstalled, GCM will discard that message right away and invalidate the registration ID. Future attempts to send a message to that device will get a NotRegistered error. See How Unregistration Works for more information.
-Although is not possible to track the status of each individual message, the Google APIs Console stats are broken down by messages sent to device, messages collapsed, and messages waiting for delivery.
+If the device is connected but idle, the message will still be
+delivered right away unless the delay_while_idle flag is set to true.
+Otherwise, it will be stored in the GCM servers until the device is awake. And
+that's where the collapse_key flag plays a role: if there is already
+a message with the same collapse key (and registration ID) stored and waiting for
+delivery, the old message will be discarded and the new message will take its place
+(that is, the old message will be collapsed by the new one). However, if the collapse
+key is not set, both the new and old messages are stored for future delivery.
+Collapsible messages are also called send-to-sync messages.
+
+Note: There is a limit on how many messages can
+be stored without collapsing. That limit is currently 100. If the limit is reached,
+all stored messages are discarded. Then when the device is back online, it receives
+a special message indicating that the limit was reached. The application can then
+handle the situation properly, typically by requesting a full sync.
+
+Likewise, there is a limit on how many collapse_key s you can have for
+a particular device. GCM allows a maximum of 4 different collapse keys to be used
+by the GCM server per device
+any given time. In other words, the GCM server can simultaneously store 4 different
+send-to-sync messages, each with a different collapse key. If you exceed this number
+GCM will only keep 4 collapse keys, with no guarantees about which ones they will be.
+See Send-to-sync messages for more information.
+
+
+If the device is not connected to GCM, the message will be stored until a
+connection is established (again respecting the collapse key rules). When a connection
+is established, GCM will deliver all pending messages to the device, regardless of
+the delay_while_idle flag. If the device never gets connected again
+(for instance, if it was factory reset), the message will eventually time out and
+be discarded from GCM storage. The default timeout is 4 weeks, unless the
+time_to_live flag is set.
+
+Finally, when GCM attempts to deliver a message to the device and the
+application was uninstalled, GCM will discard that message right away and
+invalidate the registration ID. Future attempts to send a message to that device
+will get a NotRegistered error. See
+How Unregistration Works for more information.
+Although is not possible to track the status of each individual message, the
+Google APIs Console stats are broken down by messages sent to device, messages
+collapsed, and messages waiting for delivery.
Throttling
To prevent abuse (such as sending a flood of messages to a device) and
@@ -74,107 +119,112 @@ belonging to a non-throttled category by GCM for network and battery
efficiency reasons.
Keeping the Registration State in Sync
-Whenever the application receives a com.google.android.c2dm.intent.REGISTRATION intent with a registration_id extra, it should save the ID for future use, pass it to the 3rd-party server to complete the registration, and keep track of whether the server completed the registration. If the server fails to complete the registration, it should try again or unregister from GCM.
+Whenever the application registers as described in
+Implementing GCM Client,
+it should save the registration ID for future use, pass it to the
+3rd-party server to complete the registration, and keep track of
+whether the server completed the registration. If the server fails
+to complete the registration, it should try again or unregister from GCM.
+
There are also two other scenarios that require special care:
- Application update
- Backup and restore
-When an application is updated, it should invalidate its existing registration ID, as it is not guaranteed to work with the new version. Because there is no lifecycle method called when the application is updated, the best way to achieve this validation is by storing the current application version when a registration ID is stored. Then when the application is started, compare the stored value with the current application version. If they do not match, invalidate the stored data and start the registration process again.
-
-Similarly, you should not save the registration ID when an application is backed up. This is because the registration ID could become invalid by the time the application is restored, which would put the application in an invalid state (that is, the application thinks it is registered, but the server and GCM do not store that registration ID anymore—thus the application will not get more messages).
+When an application is updated, it should invalidate its existing registration
+ID, as it is not guaranteed to work with the new version. Because there is no
+lifecycle method called when the application is updated, the best way to achieve
+this validation is by storing the current application version when a registration
+ID is stored. Then when the application is started, compare the stored value with
+the current application version. If they do not match, invalidate the stored data
+and start the registration process again.
+
+Similarly, you should not save the registration ID when an application is
+backed up. This is because the registration ID could become invalid by the time
+the application is restored, which would put the application in an invalid state
+(that is, the application thinks it is registered, but the server and GCM do not
+store that registration ID anymore—thus the application will not get more
+messages).
Canonical IDs
-On the server side, as long as the application is behaving well, everything should work normally. However, if a bug in the application triggers multiple registrations for the same device, it can be hard to reconcile state and you might end up with duplicate messages.
-GCM provides a facility called "canonical registration IDs" to easily recover from these situations. A canonical registration ID is defined to be the ID of the last registration requested by your application. This is the ID that the server should use when sending messages to the device.
-If later on you try to send a message using a different registration ID, GCM will process the request as usual, but it will include the canonical registration ID in the registration_id field of the response. Make sure to replace the registration ID stored in your server with this canonical ID, as eventually the ID you're using will stop working.
+On the server side, as long as the application is behaving well, everything
+should work normally. However, if a bug in the application triggers multiple
+registrations for the same device, it can be hard to reconcile state and you might
+end up with duplicate messages.
+GCM provides a facility called "canonical registration IDs" to easily
+recover from these situations. A canonical registration ID is defined to be the ID
+of the last registration requested by your application. This is the ID that the
+server should use when sending messages to the device.
+If later on you try to send a message using a different registration ID, GCM
+will process the request as usual, but it will include the canonical registration
+ID in the registration_id field of the response. Make sure to replace
+the registration ID stored in your server with this canonical ID, as eventually
+the ID you're using will stop working.
Automatic Retry Using Exponential Back-Off
-When the application receives a com.google.android.c2dm.intent.REGISTRATION intent with the error extra set as SERVICE_NOT_AVAILABLE , it should retry the failed operation (register or unregister).
-In the simplest case, if your application just calls register and GCM is not a fundamental part of the application, the application could simply ignore the error and try to register again the next time it starts. Otherwise, it should retry the previous operation using exponential back-off. In exponential back-off, each time there is a failure, it should wait twice the previous amount of time before trying again. If the register (or unregister) operation was synchronous, it could be retried in a simple loop. However, since it is asynchronous, the best approach is to schedule a pending intent to retry the operation. The following steps describe how to implement this in the MyIntentService example used above:
-
- - Create a random token to verify the origin of the retry intent:
+
When registration or unregistration fails, the app should retry the failed operation.
+In the simplest case, if your application attempts to register and GCM is not a
+fundamental part of the application, the application could simply ignore the error
+and try to register again the next time it starts. Otherwise, it should retry the
+previous operation using exponential back-off. In exponential back-off, each time
+there is a failure, it should wait twice the previous amount of time before trying
+again. If the register (or unregister) operation was synchronous, it could be retried
+in a simple loop. However, since it is asynchronous, the best approach is to schedule
+a {@link android.app.PendingIntent} to retry the operation.
- private static final String TOKEN =
- Long.toBinaryString(new Random().nextLong());
-
+Unregistration
- - Change the
handleRegistration() method so it creates the pending intent when appropriate:
-
-...
-if (error != null) {
- if ("SERVICE_NOT_AVAILABLE".equals(error)) {
- long backoffTimeMs = // get back-off time from shared preferences
- long nextAttempt = SystemClock.elapsedRealtime() + backoffTimeMs;
- Intent retryIntent = new Intent("com.example.gcm.intent.RETRY");
- retryIntent.putExtra("token", TOKEN);
- PendingIntent retryPendingIntent =
- PendingIntent.getBroadcast(context, 0, retryIntent, 0);
- AlarmManager am = (AlarmManager)
- context.getSystemService(Context.ALARM_SERVICE);
- am.set(AlarmManager.ELAPSED_REALTIME, nextAttempt, retryPendingIntent);
- backoffTimeMs *= 2; // Next retry should wait longer.
- // update back-off time on shared preferences
- } else {
- // Unrecoverable error, log it
- Log.i(TAG, "Received error: " + error);
-}
-...
- The back-off time is stored in a shared preference. This ensures that it is persistent across multiple activity launches. The name of the intent does not matter, as long as the same intent is used in the following steps.
-
- - Change the
onHandleIntent() method adding an else if case for the retry intent:
-
-...
-} else if (action.equals("com.example.gcm.intent.RETRY")) {
- String token = intent.getStringExtra("token");
- // make sure intent was generated by this class, not by a malicious app
- if (TOKEN.equals(token)) {
- String registrationId = // get from shared properties
- if (registrationId != null) {
- // last operation was attempt to unregister; send UNREGISTER intent again
- } else {
- // last operation was attempt to register; send REGISTER intent again
- }
-}
-...
-
- - Create a new instance of
MyReceiver in your activity:
-
-private final MyBroadcastReceiver mRetryReceiver = new MyBroadcastReceiver();
-
+This section explains when you should unregister in GCM and what happens
+when you do.
- - In the activity's
onCreate() method, register the new instance to receive the com.example.gcm.intent.RETRY intent:
- ...
-IntentFilter filter = new IntentFilter("com.example.gcm.intent.RETRY");
-filter.addCategory(getPackageName());
-registerReceiver(mRetryReceiver, filter);
-...
+Why you should rarely unregister
-Note: You must dynamically create a new instance of the broadcast receiver since the one defined by the manifest can only receive intents with the com.google.android.c2dm.permission.SEND permission. The permission com.google.android.c2dm.permission.SEND is a system permission and as such it cannot be granted to a regular application.
+A registration ID (regID) represents a particular Android application running
+on a particular device. You should only need to unregister in rare cases, such as
+if you want an app to stop receiving messages, or if you suspect that the regID has
+been compromised. In general, though, once an app has a regID, you shouldn't need
+to change it.
-
+In particular, you should never unregister your app as a mechanism for
+logout or for switching between users, for the following reasons:
- - In the activity's
onDestroy() method, unregister the broadcast receiver:
+
+ - A regID maps an app to a device. It isn't associated with a particular
+ logged in user. If you unregister and then re-register, GCM may return the same
+ ID or a different ID—there's no guarantee either way.
+
+ - Unregistration may take up to 5 minutes to propagate.
+ - After unregistration, re-registration may again take up to 5 minutes to
+propagate. During this time messages may be rejected due to the state of being
+unregistered, and after all this, messages may still go to the wrong user.
+
-unregisterReceiver(mRetryReceiver);
-
-How Unregistration Works
-There are two ways to unregister a device from GCM: manually and automatically.
-An Android application can manually unregister itself by issuing a com.google.android.c2dm.intent.UNREGISTER intent, which is useful when the application offers a logoff feature (so it can unregister on logoff and register again on logon). See the Architectural Overview for more discussion of this topic. This is the sequence of events when an application unregisters itself:
-
- - The application issues a
com.google.android.c2dm.intent.UNREGISTER intent, passing the package name as an extra.
- - When the GCM server is done with the unregistration, it sends a
com.google.android.c2dm.intent.REGISTRATION intent with the unregistered extra set.
- - The application then must contact the 3rd-party server so it can remove the registration ID.
- - The application should also clear its registration ID.
-
-
-An application can be automatically unregistered after it is uninstalled from the device. However, this process does not happens right away, as Android does not provide an uninstall callback. What happens in this scenario is as follows:
+
+The solution is to manage your own mapping between users, the regID, and
+individual messages:
+
+
+ - Your app server should maintain a mapping between the current user
+and the regID. This should include information about which user is supposed to
+receive a particular message.
+ - The app running on the device should check to ensure that messages it
+receives match the logged in user.
+
+
+
+How unregistration works
+
+An application can be automatically unregistered after it is uninstalled from
+the device. However, this process does not happens right away, as Android does not
+provide an uninstall callback. What happens in this scenario is as follows:
- The end user uninstalls the application.
- The 3rd-party server sends a message to GCM server.
- The GCM server sends the message to the device.
- - The GCM client receives the message and queries Package Manager about whether there are broadcast receivers configured to receive it, which returns
false .
+ - The GCM client receives the message and queries Package Manager about
+whether there are broadcast receivers configured to receive it, which returns
+
false .
- The GCM client informs the GCM server that the application was uninstalled.
- The GCM server marks the registration ID for deletion.
@@ -184,9 +234,16 @@ registerReceiver(mRetryReceiver, filter);
-Note: The GCM client is the Google Cloud Messaging framework present on the device.
+Note: The GCM client is the Google Cloud
+Messaging framework present on the device.
-Note that it might take a while for the registration ID be completely removed from GCM. Thus it is possible that messages sent during step 7 above gets a valid message ID as response, even though the message will not be delivered to the device. Eventually, the registration ID will be removed and the server will get a NotRegistered error, without any further action being required from the 3rd-party server (this scenario happens frequently while an application is being developed and tested).
+Note that it might take a while for the registration ID be completely removed
+from GCM. Thus it is possible that messages sent during step 7 above gets a valid
+message ID as response, even though the message will not be delivered to the device.
+Eventually, the registration ID will be removed and the server will get a
+NotRegistered error, without any further action being required from
+the 3rd-party server (this scenario happens frequently while an application is
+being developed and tested).
Send-to-Sync vs. Messages with Payload
@@ -196,17 +253,45 @@ registerReceiver(mRetryReceiver, filter);
- By default, it is stored by GCM for 4 weeks.
-But despite these similarities, messages can behave very differently depending on their particular settings. One major distinction between messages is whether they are collapsed (where each new message replaces the preceding message) or not collapsed (where each individual message is delivered). Every message sent in GCM is either a "send-to-sync" (collapsible) message or a "message with payload" (non-collapsible message). These concepts are described in more detail in the following sections.
+But despite these similarities, messages can behave very differently depending
+on their particular settings. One major distinction between messages is whether
+they are collapsed (where each new message replaces the preceding message) or not
+collapsed (where each individual message is delivered). Every message sent in GCM
+is either a "send-to-sync" (collapsible) message or a "message with
+payload" (non-collapsible message). These concepts are described in more
+detail in the following sections.
Send-to-sync messages
-A send-to-sync (collapsible) message is often a "tickle" that tells a mobile application to sync data from the server. For example, suppose you have an email application. When a user receives new email on the server, the server pings the mobile application with a "New mail" message. This tells the application to sync to the server to pick up the new email. The server might send this message multiple times as new mail continues to accumulate, before the application has had a chance to sync. But if the user has received 25 new emails, there's no need to preserve every "New mail" message. One is sufficient. Another example would be a sports application that updates users with the latest score. Only the most recent message is relevant, so it makes sense to have each new message replace the preceding message.
-
-The email and sports applications are cases where you would probably use the GCM collapse_key parameter. A collapse key is an arbitrary string that is used to collapse a group of like messages when the device is offline, so that only the most recent message gets sent to the client. For example, "New mail," "Updates available," and so on
-GCM allows a maximum of 4 different collapse keys to be used by the GCM server at any given time. In other words, the GCM server can simultaneously store 4 different send-to-sync messages, each with a different collapse key. If you exceed this number GCM will only keep 4 collapse keys, with no guarantees about which ones they will be.
+A send-to-sync (collapsible) message is often a "tickle" that tells
+a mobile application to sync data from the server. For example, suppose you have
+an email application. When a user receives new email on the server, the server
+pings the mobile application with a "New mail" message. This tells the
+application to sync to the server to pick up the new email. The server might send
+this message multiple times as new mail continues to accumulate, before the application
+has had a chance to sync. But if the user has received 25 new emails, there's no
+need to preserve every "New mail" message. One is sufficient. Another
+example would be a sports application that updates users with the latest score.
+Only the most recent message is relevant, so it makes sense to have each new
+message replace the preceding message.
+
+The email and sports applications are cases where you would probably use the
+GCM collapse_key parameter. A collapse key is an arbitrary
+string that is used to collapse a group of like messages when the device is offline,
+so that only the most recent message gets sent to the client. For example,
+"New mail," "Updates available," and so on
+GCM allows a maximum of 4 different collapse keys to be used by the GCM server
+at any given time. In other words, the GCM server can simultaneously store 4
+different send-to-sync messages per device, each with a different collapse key.
+For example, Device A can have A1, A2, A3, and A4. Device B can have B1, B2, B3,
+and B4, and so on. If you exceed this number GCM will only keep 4 collapse keys, with no
+guarantees about which ones they will be.
Messages with payload
-Unlike a send-to-sync message, every "message with payload" (non-collapsible message) is delivered. The payload the message contains can be up to 4kb. For example, here is a JSON-formatted message in an IM application in which spectators are discussing a sporting event:
+Unlike a send-to-sync message, every "message with payload"
+(non-collapsible message) is delivered. The payload the message contains can be
+up to 4kb. For example, here is a JSON-formatted message in an IM application in
+which spectators are discussing a sporting event:
{
"registration_id" : "APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx...",
@@ -217,19 +302,42 @@ registerReceiver(mRetryReceiver, filter);
},
}
-A "message with payload" is not simply a "ping" to the mobile application to contact the server to fetch data. In the aforementioned IM application, for example, you would want to deliver every message, because every message has different content. To specify a non-collapsible message, you simply omit the collapse_key parameter. Thus GCM will send each message individually. Note that the order of delivery is not guaranteed.
-GCM will store up to 100 non-collapsible messages. After that, all messages are discarded from GCM, and a new message is created that tells the client how far behind it is. The message is delivered through a regular com.google.android.c2dm.intent.RECEIVE intent, with the following extras:
+A "message with payload" is not simply a "ping" to the
+mobile application to contact the server to fetch data. In the aforementioned IM
+application, for example, you would want to deliver every message, because every
+message has different content. To specify a non-collapsible message, you simply
+omit the collapse_key parameter. Thus GCM will send each message
+individually. Note that the order of delivery is not guaranteed.
+
+GCM will store up to 100 non-collapsible messages. After that, all messages
+are discarded from GCM, and a new message is created that tells the client how
+far behind it is. The message is delivered through a regular
+com.google.android.c2dm.intent.RECEIVE intent, with the following
+extras:
- -
message_type —The value is always the string "deleted_messages".
- total_deleted —The value is a string with the number of deleted messages.
+ -
message_type —The value is always the string
+"deleted_messages".
+ total_deleted —The value is a string with the number of
+deleted messages.
-The application should respond by syncing with the server to recover the discarded messages.
+The application should respond by syncing with the server to recover the
+discarded messages.
Which should I use?
- If your application does not need to use non-collapsible messages, collapsible messages are a better choice from a performance standpoint, because they put less of a burden on the device battery.
+ If your application does not need to use non-collapsible messages, collapsible
+messages are a better choice from a performance standpoint, because they put less
+of a burden on the device battery. However, if you use collapsible messages, remember that
+GCM only allows a maximum of 4 different collapse keys to be used by the GCM server
+per device at any given time. You must not exceed this number, or it could cause
+unpredictable consequences.
Setting an Expiration Date for a Message
-The Time to Live (TTL) feature lets the sender specify the maximum lifespan of a message using the time_to_live parameter in the send request. The value of this parameter must be a duration from 0 to 2,419,200 seconds, and it corresponds to the maximum period of time for which GCM will store and try to deliver the message. Requests that don't contain this field default to the maximum period of 4 weeks.
+The Time to Live (TTL) feature lets the sender specify the maximum lifespan
+of a message using the time_to_live parameter in the send request.
+The value of this parameter must be a duration from 0 to 2,419,200 seconds, and
+it corresponds to the maximum period of time for which GCM will store and try to
+deliver the message. Requests that don't contain this field default to the maximum
+period of 4 weeks.
Here are some possible uses for this feature:
- Video chat incoming calls
@@ -237,9 +345,29 @@ registerReceiver(mRetryReceiver, filter);
- Calendar events
Background
-GCM will usually deliver messages immediately after they are sent. However, this might not always be possible. For example, the device could be turned off, offline, or otherwise unavailable. In other cases, the sender itself might request that messages not be delivered until the device becomes active by using the delay_while_idle flag. Finally, GCM might intentionally delay messages to prevent an application from consuming excessive resources and negatively impacting battery life.
-When this happens, GCM will store the message and deliver it as soon as it's feasible. While this is fine in most cases, there are some applications for which a late message might as well never be delivered. For example, if the message is an incoming call or video chat notification, it will only be meaningful for a small period of time before the call is terminated. Or if the message is an invitation to an event, it will be useless if received after the event has ended.
-Another advantage of specifying the expiration date for a message is that GCM will never throttle messages with a time_to_live value of 0 seconds. In other words, GCM will guarantee best effort for messages that must be delivered "now or never." Keep in mind that a time_to_live value of 0 means messages that can't be delivered immediately will be discarded. However, because such messages are never stored, this provides the best latency for sending notifications.
+GCM will usually deliver messages immediately after they are sent. However,
+this might not always be possible. For example, the device could be turned off,
+offline, or otherwise unavailable. In other cases, the sender itself might request
+that messages not be delivered until the device becomes active by using the
+delay_while_idle flag. Finally, GCM might intentionally delay messages
+to prevent an application from consuming excessive resources and negatively
+impacting battery life.
+
+When this happens, GCM will store the message and deliver it as soon as it's
+feasible. While this is fine in most cases, there are some applications for which
+a late message might as well never be delivered. For example, if the message is
+an incoming call or video chat notification, it will only be meaningful for a
+small period of time before the call is terminated. Or if the message is an
+invitation to an event, it will be useless if received after the event has ended.
+
+Another advantage of specifying the expiration date for a message is that GCM
+will never throttle messages with a time_to_live value of 0 seconds.
+In other words, GCM will guarantee best effort for messages that must be delivered
+"now or never." Keep in mind that a time_to_live value of
+0 means messages that can't be delivered immediately will be discarded. However,
+because such messages are never stored, this provides the best latency for
+sending notifications.
+
Here is an example of a JSON-formatted request that includes TTL:
{
@@ -256,9 +384,23 @@ registerReceiver(mRetryReceiver, filter);
Receiving Messages from Multiple Senders
-GCM allows multiple parties to send messages to the same application. For example, suppose your application is an articles aggregator with multiple contributors, and you want each of them to be able to send a message when they publish a new article. This message might contain a URL so that the application can download the article. Instead of having to centralize all sending activity in one location, GCM gives you the ability to let each of these contributors send its own messages.
-To make this possible, all you need to do is have each sender generate its own project number. Then include those IDs in the sender field, separated by commas, when requesting a registration. Finally, share the registration ID with your partners, and they'll be able to send messages to your application using their own authentication keys.
-This code snippet illustrates this feature. Senders are passed as an intent extra in a comma-separated list:
+
+GCM allows multiple parties to send messages to the same application. For
+example, suppose your application is an articles aggregator with multiple
+contributors, and you want each of them to be able to send a message when they
+publish a new article. This message might contain a URL so that the application
+can download the article. Instead of having to centralize all sending activity in
+one location, GCM gives you the ability to let each of these contributors send
+its own messages.
+
+To make this possible, all you need to do is have each sender generate its own
+project number. Then include those IDs in the sender field, separated by commas,
+when requesting a registration. Finally, share the registration ID with your
+partners, and they'll be able to send messages to your application using their
+own authentication keys.
+This code snippet illustrates this feature. Senders are passed as an intent
+extra in a comma-separated list:
+
Intent intent = new Intent(GCMConstants.INTENT_TO_GCM_REGISTRATION);
intent.setPackage(GSF_PACKAGE);
intent.putExtra(GCMConstants.EXTRA_APPLICATION_PENDING_INTENT,
@@ -269,4 +411,3 @@ ontext.startService(intent);
Note that there is limit of 100 multiple senders.
-
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 0cadbd245c16bc1d18a3ee4ca62791d3b744d10a..d2177ca8a8975e7f726c6d6b39af7a7f225675d6 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -1,93 +1,96 @@
-page.title=GCM Cloud Connection Server
+page.title=GCM Cloud Connection Server (XMPP)
@jd:body
-Note: To try out this feature, sign up using this form.
+Note: To try out this feature, sign up using
+this form.
-The GCM Cloud Connection Server (CCS) allows third party servers to communicate with Android devices by establishing a persistent TCP connection with Google servers using the XMPP protocol. This communication is asynchronous and bidirectional.
-You can continue to use the HTTP request mechanism to send messages to GCM servers, side-by-side with CCS which uses XMPP. Some of the benefits of CCS include:
+The GCM Cloud Connection Server (CCS) is a connection server based on XMPP.
+CCS allows 3rd-party app servers (which you're
+responsible for implementing) to communicate
+with Android devices by establishing a persistent TCP connection with Google
+servers using the XMPP protocol. This communication is asynchronous and bidirectional.
+You can continue to use the HTTP request mechanism to send messages to GCM
+servers, side-by-side with CCS which uses XMPP. Some of the benefits of CCS include:
- - The asynchronous nature of XMPP allows you to send more messages with fewer resources.
- - Communication is bidirectional—not only can the server send messages to the device, but the device can send messages back to the server.
-- You can send messages back using the same connection used for receiving, thereby improving battery life.
+ - The asynchronous nature of XMPP allows you to send more messages with fewer
+resources.
+ - Communication is bidirectional—not only can the server send messages
+to the device, but the device can send messages back to the server.
+- You can send messages back using the same connection used for receiving,
+thereby improving battery life.
-The upstream messaging (device-to-cloud) feature of CCS is part of the Google Play services platform. Upstream messaging is available through the {@code GoogleCloudMessaging} APIs. To use upstream messaging and the new streamlined registration process, you must set up the Google Play services SDK.
+The upstream messaging (device-to-cloud) feature of CCS is part of the Google
+Play services platform. Upstream messaging is available through the
+
+{@code GoogleCloudMessaging}
+APIs. For examples, see
+Implementing an XMPP-based App Server.
-Note: For an example of an XMPP server, see GCM Server.
-
- CCS vs. GCM HTTP
-
-CCS messaging differs from GCM HTTP messaging in the following ways:
-
- - Upstream/Downstream messages
-
- - GCM HTTP: Downstream only: cloud-to-device.
- - CCS: Upstream and downstream (device-to-cloud, cloud-to-device).
-
-
- - Asynchronous messaging
-
- - GCM HTTP: 3rd-party servers send messages as HTTP POST requests and wait for a response. This mechanism is synchronous and causes the sender to block before sending another message.
- - CCS: 3rd-party servers connect to Google infrastructure using a persistent XMPP connection and send/receive messages to/from all their devices at full line speed. CCS sends acknowledgements or failure notifications (in the form of special ACK and NACK JSON-encoded XMPP messages) asynchronously.
-
-
+Note: See
+Implementing GCM Server for a list of all the message
+parameters and which connection server(s) supports them.
- - JSON
-
- - GCM HTTP: JSON messages sent as HTTP POST.
- - CCS: JSON messages encapsulated in XMPP messages.
-
-
-
-This document describes how to use CCS. For general concepts and information on how to use GCM HTTP, see the GCM Architectural Overview.
How to Use CCS
-GCM Cloud Connection Server (CCS) is an XMPP endpoint, running on {@code http://gcm.googleapis.com} port 5235.
+GCM Cloud Connection Server (CCS) is an XMPP endpoint, running on
+{@code http://gcm.googleapis.com} port 5235.
-CCS requires a Transport Layer Security (TLS) connection. That means the XMPP client must initiate a TLS connection.
-For example in smack, you would call {@code setSocketFactory(SSLSocketFactory)}, similar to “old style SSL” XMPP connections and https.
+CCS requires a Transport Layer Security (TLS) connection. That means the XMPP
+client must initiate a TLS connection.
+For example in Java, you would call {@code setSocketFactory(SSLSocketFactory)}.
-CCS requires a SASL PLAIN authentication mechanism using {@code <your_GCM_Sender_Id>@gcm.googleapis.com} (GCM sender ID) and the API key as the password, where the sender ID and API key are the same as described in Getting Started.
+CCS requires a SASL PLAIN authentication mechanism using
+{@code <your_GCM_Sender_Id>@gcm.googleapis.com} (GCM sender ID) and the
+API key as the password, where the sender ID and API key are the same as described
+in Getting Started.
You can use most XMPP libraries to interact with CCS.
-Sending messages
+Authentication
The following snippets illustrate how to perform authentication in CCS.
Client
@@ -108,13 +111,13 @@ For example in smack, you would call {@code setSocketFactory(SSLSocketFactory)},
Client
<auth mechanism="PLAIN"
xmlns="urn:ietf:params:xml:ns:xmpp-sasl">MTI2MjAwMzQ3OTMzQHByb2plY3RzLmdjbS5hb
-mRyb2lkLmNvbQAxMjYyMDAzNDc5FzNAcHJvamVjdHMtZ2EtLmFuZHJvaWQuY29tAEFJe
mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==</auth>
+
Server
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>
-
+
CCS uses normal XMPP <message> stanzas. The body of the message must be:
@@ -123,25 +126,42 @@ mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==</auth>
</gcm>
-The JSON payload for server-to-device is similar to what the GCM http endpoint uses, with these exceptions:
+The JSON payload for server-to-device is similar to what the GCM http endpoint
+uses, with these exceptions:
-For each message a device sends to the server, you need to send an ACK message. You never need to send a NACK message. If you don't send an ACK for a message, CCS will just resend it.
+ For each device message your app server receives from CCS, it needs to send
+an ACK message.
+It never needs to send a NACK message. If you don't send an ACK for a message,
+CCS will just resend it.
-CCS also sends an ACK or NACK for each server-to-device message. If you do not receive either, it means that the TCP connection was closed in the middle of the operation and your server needs to resend the messages.
+ CCS also sends an ACK or NACK for each server-to-device message. If you do not
+receive either, it means that the TCP connection was closed in the middle of the
+operation and your server needs to resend the messages. See
+Flow Control for details.
-Message Examples
+Note: See
+Implementing GCM Server for a list of all the message
+parameters and which connection server(s) supports them.
-Here is an XMPP stanza containing the JSON message from a 3rd-party server to CCS:
+ Request format
+
+Here is an XMPP stanza containing the JSON message from a 3rd-party app server to CCS:
<message id="">
@@ -160,7 +180,15 @@ mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==</auth>
</message>
-Here is an XMPP stanza containing the ACK/NACK message from CCS to 3rd-party server:
+ Response format
+
+A CCS response can have 3 possible forms. The first one is a regular 'ack'
+message. But when the response contains an error, there are 2
+different forms the message can take, described below.
+
+ACK message
+
+Here is an XMPP stanza containing the ACK/NACK message from CCS to 3rd-party app server:
<message id="">
<gcm xmlns="google:mobile:data">
@@ -171,24 +199,138 @@ mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==</auth>
}
</gcm>
</message>
+
-<message id="">
- <gcm xmlns="google:mobile:data">
+NACK message
+
+A NACK error is a regular XMPP message in which the {@code message_type} status
+message is "nack". A NACK message contains:
+
+- Nack error code.
+- Nack error description.
+
+
+Below are some examples.
+
+Bad registration:
+<message>
+ <data:gcm xmlns:data="google:mobile:data">
{
- "from":"REGID",
- "message_id":"m-1366082849205"
- "error": ERROR_CODE,
- "message_type":"nack"
+ "error":"BAD_REGISTRATION", // error code
+ "message_id":"msgId1",
+ "from":"PA91bHFOtaQGSwupt5l1og",
+ "message_type":"nack"
+ }
+ </data:gcm>
+</message>
+
+Invalid "time to live":
+
+<message>
+ <data:gcm xmlns:data="google:mobile:data">
+ {
+ "error":"InvalidJson : INVALID_TTL : Invalid value (-1) for \"time_to_live\": must be between 0 and \"2419200\"\n",
+ "message_id":"msgId1",
+ "from":"APA91bHFOtaQGSwupt5l1og",
+ "message_type":"nack"
+ }
+ </data:gcm>
+</message>
+
+JSON type error:
+
+<message>
+ <data:gcm xmlns:data="google:mobile:data">
+ {
+ "error":"InvalidJson : JSON_TYPE_ERROR : Field \"delay_while_idle\" must be a JSON java.lang.Boolean: not-boolean-user-supplied-value\n",
+ "message_id":"msgId1",
+ "from":"APA91bHFOtaQGSwupt5l1og",
+ "message_type":"nack"
}
+ </data:gcm>
+</message>
+
+
+The following table lists some of the more common NACK error codes.
+
+
+ Table 1. NACK error codes.
+
+
+
+Error Code |
+Description |
+
+
+{@code BAD_REGISTRATION} |
+The device has a registration ID, but it's invalid. |
+
+
+{@code DEVICE_UNREGISTERED} |
+The device is not registered. |
+
+
+{@code INTERNAL_SERVER_ERROR} |
+The server encountered an error while trying to process the request. |
+
+
+{@code SERVICE_UNAVAILABLE} |
+The CCS connection server is temporarily unavailable, try again later
+(using exponential backoff, etc.). |
+
+
+{@code BAD_ACK} |
+The ACK message is improperly formed. |
+
+
+{@code AUTHENTICATION_FAILED} |
+This is a 401 error indicating that there was an error authenticating the sender account. |
+
+
+{@code INVALID_TTL} |
+There was an error in the supplied "time to live" value. |
+
+
+{@code JSON_TYPE_ERROR} |
+There was an error in the supplied JSON data type. |
+
+
+
+Stanza error
+
+You can also get a stanza error in certain cases.
+A stanza error contains:
+
+- Stanza error code.
+- Stanza error description (free text).
+
+For example:
+
+<message id="3" type="error" to="123456789@gcm.googleapis.com/ABC">
+ <gcm xmlns="google:mobile:data">
+ {"random": "text"}
</gcm>
+ <error code="400" type="modify">
+ <bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
+ InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n
+ </text>
+ </error>
</message>
-Upstream Messages
-Using CCS and the GoogleCloudMessaging API, you can send messages from a user's device to the cloud.
+Upstream Messages
-Here is how you send an upstream message using the GoogleCloudMessaging API. For a complete example, see Getting Started:
+Using CCS and the
+
+{@code GoogleCloudMessaging}
+API, you can send messages from a user's device to the cloud.
+
+Here is how you send an upstream message using the
+
+{@code GoogleCloudMessaging}
+API. For a complete example, see Implementing GCM Client:
GoogleCloudMessaging gcm = GoogleCloudMessaging.get(context);
String GCM_SENDER_ID = "Your-Sender-ID";
@@ -198,12 +340,15 @@ Bundle data = new Bundle();
// Bundle data consists of a key-value pair
data.putString("hello", "world");
// "time to live" parameter
+// This is optional. It specifies a value in seconds up to 4 weeks.
int ttl = [0 seconds, 4 weeks]
gcm.send(GCM_SENDER_ID + "@gcm.googleapis.com", id, ttl, data);
-This call generates the necessary XMPP stanza for sending the upstream message. The message goes from the app on the device to CCS to the 3rd-party server. The stanza has the following format:
+This call generates the necessary XMPP stanza for sending the upstream message.
+The message goes from the app on the device to CCS to the 3rd-party app server.
+The stanza has the following format:
<message id="">
<gcm xmlns="google:mobile:data">
@@ -219,7 +364,8 @@ gcm.send(GCM_SENDER_ID + "@gcm.googleapis.com", id, ttl, data);
</gcm>
</message>
-Here is the format of the ACK expected by CCS from 3rd-party servers in response to the above message:
+Here is the format of the ACK expected by CCS from 3rd-party app servers in
+response to the above message:
<message id="">
<gcm xmlns="google:mobile:data">
@@ -231,13 +377,478 @@ gcm.send(GCM_SENDER_ID + "@gcm.googleapis.com", id, ttl, data);
</gcm>
</message>
-
Flow Control
-Every message sent to CCS receives either an ACK or a NACK response. Messages that haven't received one of these responses are considered pending. If the pending message count reaches 1000, the 3rd-party server should stop sending new messages and wait for CCS to acknowledge some of the existing pending messages.
+Every message sent to CCS receives either an ACK or a NACK response. Messages
+that haven't received one of these responses are considered pending. If the pending
+message count reaches 1000, the 3rd-party app server should stop sending new messages
+and wait for CCS to acknowledge some of the existing pending messages as illustrated in
+figure 1:
+
+
-Conversely, to avoid overloading the 3rd-party server, CCS will stop sending if there are too many unacknowledged messages. Therefore, the 3rd-party server should "ACK" received messages as soon as possible to maintain a constant flow of incoming messages. The aforementioned pending message limit doesn't apply to these ACKs. Even if the pending message count reaches 1000, the 3rd-party server should continue sending ACKs to avoid blocking delivery of new messages.
+
+ Figure 1. Message/ack flow.
+
-ACKs are only valid within the context of one connection. If the connection is closed before a message can be ACKed, the 3rd-party server should wait for CCS to resend the message before ACKing it again.
+ Conversely, to avoid overloading the 3rd-party app server, CCS will stop sending
+if there are too many unacknowledged messages. Therefore, the 3rd-party app server
+should "ACK" upstream messages, received from the client application via CCS, as soon as possible
+to maintain a constant flow of incoming messages. The aforementioned pending message limit doesn't
+apply to these ACKs. Even if the pending message count reaches 1000, the 3rd-party app server
+should continue sending ACKs for messages received from CCS to avoid blocking delivery of new
+upstream messages.
+
+ACKs are only valid within the context of one connection. If the connection is
+closed before a message can be ACKed, the 3rd-party app server should wait for CCS
+to resend the upstream message before ACKing it again. Similarly, all pending messages for which an
+ACK/NACK was not received from CCS before the connection was closed should be sent again.
+Implementing an XMPP-based App Server
+
+This section gives examples of implementing an app server that works with CCS.
+Note that a full GCM implementation requires a client-side implementation, in
+addition to the server. For more information, see
+Implementing GCM Client.
+
+ Java sample using the Smack library
+
+Here is a sample app server written in Java, using the
+Smack library.
+
+import org.jivesoftware.smack.ConnectionConfiguration;
+import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
+import org.jivesoftware.smack.ConnectionListener;
+import org.jivesoftware.smack.PacketInterceptor;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.DefaultPacketExtension;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smack.provider.ProviderManager;
+import org.jivesoftware.smack.util.StringUtils;
+import org.json.simple.JSONValue;
+import org.json.simple.parser.ParseException;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLSocketFactory;
+/**
+ * Sample Smack implementation of a client for GCM Cloud Connection Server.
+ *
+ * <p>For illustration purposes only.
+ */
+public class SmackCcsClient {
+
+ Logger logger = Logger.getLogger("SmackCcsClient");
+
+ public static final String GCM_SERVER = "gcm.googleapis.com";
+ public static final int GCM_PORT = 5235;
+
+ public static final String GCM_ELEMENT_NAME = "gcm";
+ public static final String GCM_NAMESPACE = "google:mobile:data";
+
+ static Random random = new Random();
+ XMPPConnection connection;
+ ConnectionConfiguration config;
+
+ /**
+ * XMPP Packet Extension for GCM Cloud Connection Server.
+ */
+ class GcmPacketExtension extends DefaultPacketExtension {
+ String json;
+
+ public GcmPacketExtension(String json) {
+ super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
+ this.json = json;
+ }
+
+ public String getJson() {
+ return json;
+ }
+
+ @Override
+ public String toXML() {
+ return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
+ GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
+ }
+
+ @SuppressWarnings("unused")
+ public Packet toPacket() {
+ return new Message() {
+ // Must override toXML() because it includes a <body>
+ @Override
+ public String toXML() {
+
+ StringBuilder buf = new StringBuilder();
+ buf.append("<message");
+ if (getXmlns() != null) {
+ buf.append(" xmlns=\"").append(getXmlns()).append("\"");
+ }
+ if (getLanguage() != null) {
+ buf.append(" xml:lang=\"").append(getLanguage()).append("\"");
+ }
+ if (getPacketID() != null) {
+ buf.append(" id=\"").append(getPacketID()).append("\"");
+ }
+ if (getTo() != null) {
+ buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\"");
+ }
+ if (getFrom() != null) {
+ buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
+ }
+ buf.append(">");
+ buf.append(GcmPacketExtension.this.toXML());
+ buf.append("</message>");
+ return buf.toString();
+ }
+ };
+ }
+ }
+
+ public SmackCcsClient() {
+ // Add GcmPacketExtension
+ ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
+ GCM_NAMESPACE, new PacketExtensionProvider() {
+
+ @Override
+ public PacketExtension parseExtension(XmlPullParser parser)
+ throws Exception {
+ String json = parser.nextText();
+ GcmPacketExtension packet = new GcmPacketExtension(json);
+ return packet;
+ }
+ });
+ }
+
+ /**
+ * Returns a random message id to uniquely identify a message.
+ *
+ * <p>Note:
+ * This is generated by a pseudo random number generator for illustration purpose,
+ * and is not guaranteed to be unique.
+ *
+ */
+ public String getRandomMessageId() {
+ return "m-" + Long.toString(random.nextLong());
+ }
+
+ /**
+ * Sends a downstream GCM message.
+ */
+ public void send(String jsonRequest) {
+ Packet request = new GcmPacketExtension(jsonRequest).toPacket();
+ connection.sendPacket(request);
+ }
+
+ /**
+ * Handles an upstream data message from a device application.
+ *
+ * <p>This sample echo server sends an echo message back to the device.
+ * Subclasses should override this method to process an upstream message.
+ */
+ public void handleIncomingDataMessage(Map<String, Object> jsonObject) {
+ String from = jsonObject.get("from").toString();
+
+ // PackageName of the application that sent this message.
+ String category = jsonObject.get("category").toString();
+
+ // Use the packageName as the collapseKey in the echo packet
+ String collapseKey = "echo:CollapseKey";
+ @SuppressWarnings("unchecked")
+ Map<String, String> payload = (Map<String, String>) jsonObject.get("data");
+ payload.put("ECHO", "Application: " + category);
+
+ // Send an ECHO response back
+ String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false);
+ send(echo);
+ }
+
+ /**
+ * Handles an ACK.
+ *
+ * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to
+ * properly handle ACKS.
+ */
+ public void handleAckReceipt(Map<String, Object> jsonObject) {
+ String messageId = jsonObject.get("message_id").toString();
+ String from = jsonObject.get("from").toString();
+ logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId);
+ }
+
+ /**
+ * Handles a NACK.
+ *
+ * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to
+ * properly handle NACKS.
+ */
+ public void handleNackReceipt(Map<String, Object> jsonObject) {
+ String messageId = jsonObject.get("message_id").toString();
+ String from = jsonObject.get("from").toString();
+ logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId);
+ }
+
+ /**
+ * Creates a JSON encoded GCM message.
+ *
+ * @param to RegistrationId of the target device (Required).
+ * @param messageId Unique messageId for which CCS will send an "ack/nack" (Required).
+ * @param payload Message content intended for the application. (Optional).
+ * @param collapseKey GCM collapse_key parameter (Optional).
+ * @param timeToLive GCM time_to_live parameter (Optional).
+ * @param delayWhileIdle GCM delay_while_idle parameter (Optional).
+ * @return JSON encoded GCM message.
+ */
+ public static String createJsonMessage(String to, String messageId, Map<String, String> payload,
+ String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
+ Map<String, Object> message = new HashMap<String, Object>();
+ message.put("to", to);
+ if (collapseKey != null) {
+ message.put("collapse_key", collapseKey);
+ }
+ if (timeToLive != null) {
+ message.put("time_to_live", timeToLive);
+ }
+ if (delayWhileIdle != null && delayWhileIdle) {
+ message.put("delay_while_idle", true);
+ }
+ message.put("message_id", messageId);
+ message.put("data", payload);
+ return JSONValue.toJSONString(message);
+ }
+
+ /**
+ * Creates a JSON encoded ACK message for an upstream message received from an application.
+ *
+ * @param to RegistrationId of the device who sent the upstream message.
+ * @param messageId messageId of the upstream message to be acknowledged to CCS.
+ * @return JSON encoded ack.
+ */
+ public static String createJsonAck(String to, String messageId) {
+ Map<String, Object> message = new HashMap<String, Object>();
+ message.put("message_type", "ack");
+ message.put("to", to);
+ message.put("message_id", messageId);
+ return JSONValue.toJSONString(message);
+ }
+
+ /**
+ * Connects to GCM Cloud Connection Server using the supplied credentials.
+ *
+ * @param username GCM_SENDER_ID@gcm.googleapis.com
+ * @param password API Key
+ * @throws XMPPException
+ */
+ public void connect(String username, String password) throws XMPPException {
+ config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
+ config.setSecurityMode(SecurityMode.enabled);
+ config.setReconnectionAllowed(true);
+ config.setRosterLoadedAtLogin(false);
+ config.setSendPresence(false);
+ config.setSocketFactory(SSLSocketFactory.getDefault());
+
+ // NOTE: Set to true to launch a window with information about packets sent and received
+ config.setDebuggerEnabled(true);
+
+ // -Dsmack.debugEnabled=true
+ XMPPConnection.DEBUG_ENABLED = true;
+
+ connection = new XMPPConnection(config);
+ connection.connect();
+
+ connection.addConnectionListener(new ConnectionListener() {
+
+ @Override
+ public void reconnectionSuccessful() {
+ logger.info("Reconnecting..");
+ }
+
+ @Override
+ public void reconnectionFailed(Exception e) {
+ logger.log(Level.INFO, "Reconnection failed.. ", e);
+ }
+
+ @Override
+ public void reconnectingIn(int seconds) {
+ logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
+ }
+
+ @Override
+ public void connectionClosedOnError(Exception e) {
+ logger.log(Level.INFO, "Connection closed on error.");
+ }
+
+ @Override
+ public void connectionClosed() {
+ logger.info("Connection closed.");
+ }
+ });
+
+ // Handle incoming packets
+ connection.addPacketListener(new PacketListener() {
+
+ @Override
+ public void processPacket(Packet packet) {
+ logger.log(Level.INFO, "Received: " + packet.toXML());
+ Message incomingMessage = (Message) packet;
+ GcmPacketExtension gcmPacket =
+ (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
+ String json = gcmPacket.getJson();
+ try {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> jsonObject =
+ (Map<String, Object>) JSONValue.parseWithException(json);
+
+ // present for "ack"/"nack", null otherwise
+ Object messageType = jsonObject.get("message_type");
+
+ if (messageType == null) {
+ // Normal upstream data message
+ handleIncomingDataMessage(jsonObject);
+
+ // Send ACK to CCS
+ String messageId = jsonObject.get("message_id").toString();
+ String from = jsonObject.get("from").toString();
+ String ack = createJsonAck(from, messageId);
+ send(ack);
+ } else if ("ack".equals(messageType.toString())) {
+ // Process Ack
+ handleAckReceipt(jsonObject);
+ } else if ("nack".equals(messageType.toString())) {
+ // Process Nack
+ handleNackReceipt(jsonObject);
+ } else {
+ logger.log(Level.WARNING, "Unrecognized message type (%s)",
+ messageType.toString());
+ }
+ } catch (ParseException e) {
+ logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
+ } catch (Exception e) {
+ logger.log(Level.SEVERE, "Couldn't send echo.", e);
+ }
+ }
+ }, new PacketTypeFilter(Message.class));
+
+
+ // Log all outgoing packets
+ connection.addPacketInterceptor(new PacketInterceptor() {
+ @Override
+ public void interceptPacket(Packet packet) {
+ logger.log(Level.INFO, "Sent: {0}", packet.toXML());
+ }
+ }, new PacketTypeFilter(Message.class));
+
+ connection.login(username, password);
+ }
+
+ public static void main(String [] args) {
+ final String userName = "Your GCM Sender Id" + "@gcm.googleapis.com";
+ final String password = "API Key";
+
+ SmackCcsClient ccsClient = new SmackCcsClient();
+
+ try {
+ ccsClient.connect(userName, password);
+ } catch (XMPPException e) {
+ e.printStackTrace();
+ }
+
+ // Send a sample hello downstream message to a device.
+ String toRegId = "RegistrationIdOfTheTargetDevice";
+ String messageId = ccsClient.getRandomMessageId();
+ Map<String, String> payload = new HashMap<String, String>();
+ payload.put("Hello", "World");
+ payload.put("CCS", "Dummy Message");
+ payload.put("EmbeddedMessageId", messageId);
+ String collapseKey = "sample";
+ Long timeToLive = 10000L;
+ Boolean delayWhileIdle = true;
+ ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey,
+ timeToLive, delayWhileIdle));
+ }
+}
+Python sample
+
+Here is an example of a CCS app server written in Python. This sample echo
+server sends an initial message, and for every upstream message received, it sends
+a dummy response back to the application that sent the upstream message. This
+example illustrates how to connect, send, and receive GCM messages using XMPP. It
+shouldn't be used as-is on a production deployment.
+
+
+#!/usr/bin/python
+import sys, json, xmpp, random, string
+
+SERVER = 'gcm.googleapis.com'
+PORT = 5235
+USERNAME = "Your GCM Sender Id"
+PASSWORD = "API Key"
+REGISTRATION_ID = "Registration Id of the target device"
+
+unacked_messages_quota = 1000
+send_queue = []
+
+# Return a random alphanumerical id
+def random_id():
+ rid = ''
+ for x in range(8): rid += random.choice(string.ascii_letters + string.digits)
+ return rid
+
+def message_callback(session, message):
+ global unacked_messages_quota
+ gcm = message.getTags('gcm')
+ if gcm:
+ gcm_json = gcm[0].getData()
+ msg = json.loads(gcm_json)
+ if not msg.has_key('message_type'):
+ # Acknowledge the incoming message immediately.
+ send({'to': msg['from'],
+ 'message_type': 'ack',
+ 'message_id': msg['message_id']})
+ # Queue a response back to the server.
+ if msg.has_key('from'):
+ # Send a dummy echo response back to the app that sent the upstream message.
+ send_queue.append({'to': msg['from'],
+ 'message_id': random_id(),
+ 'data': {'pong': 1}})
+ elif msg['message_type'] == 'ack' or msg['message_type'] == 'nack':
+ unacked_messages_quota += 1
+
+def send(json_dict):
+ template = ("<message><gcm xmlns='google:mobile:data'>{1}</gcm></message>")
+ client.send(xmpp.protocol.Message(
+ node=template.format(client.Bind.bound[0], json.dumps(json_dict))))
+
+def flush_queued_messages():
+ global unacked_messages_quota
+ while len(send_queue) and unacked_messages_quota > 0:
+ send(send_queue.pop(0))
+ unacked_messages_quota -= 1
+
+client = xmpp.Client('gcm.googleapis.com', debug=['socket'])
+client.connect(server=(SERVER,PORT), secure=1, use_srv=False)
+auth = client.auth(USERNAME, PASSWORD)
+if not auth:
+ print 'Authentication failed!'
+ sys.exit(1)
+
+client.RegisterHandler('message', message_callback)
+
+send_queue.append({'to': REGISTRATION_ID,
+ 'message_id': 'reg_id',
+ 'data': {'message_destination': 'RegId',
+ 'message_id': random_id()}})
+
+while True:
+ client.Process(1)
+ flush_queued_messages()
diff --git a/docs/html/google/gcm/client.jd b/docs/html/google/gcm/client.jd
index 7604932aa383279c585099676723ec27929dfc84..df357a2703090200e28dc72f8f911a52b2f617d0 100644
--- a/docs/html/google/gcm/client.jd
+++ b/docs/html/google/gcm/client.jd
@@ -1,24 +1,663 @@
-page.title=GCM Client
+page.title=Implementing GCM Client
page.tags="cloud","push","messaging"
@jd:body
-A GCM client is a GCM-enabled app that runs on an Android device. To write your client code, we recommend that you use the new {@code GoogleCloudMessaging} APIs. The client helper library that was offered in previous versions of GCM still works, but it has been superseded by the more efficient {@code GoogleCloudMessaging} APIs.
+A GCM client is a GCM-enabled app that runs on an Android device. To write your
+client code, we recommend that you use the
+
+{@code GoogleCloudMessaging} APIs.
+The client helper library that was offered in previous versions of GCM still works,
+but it has been superseded by the more efficient
+
+{@code GoogleCloudMessaging} APIs.
+
+A full GCM implementation requires both a client implementation and a server
+implementation. For more
+information about implementing the server side, see
+Implementing GCM Server.
+
+The following sections walk you through the steps involved in writing a GCM
+client-side application. Your client app can be arbitrarily complex, but at bare
+minimum, a GCM client app must include code to register (and thereby get a
+registration ID), and a broadcast receiver to receive messages sent by GCM.
+
+
+Step 1: Set Up Google Play Services
+
+To write your client application, use the
+
+{@code GoogleCloudMessaging} API.
+To use this API, you must set up your project to use the Google Play services SDK,
+as described in Setup Google Play
+Services SDK.
+
+Caution: When you add the Play Services library to
+your project, be sure to add it with resources, as described in
+
+Setup Google Play Services SDK. The key point is that you must
+reference the library—simply adding a {@code .jar} file to
+your Eclipse project will not work. You must follow the directions
+for referencing a library, or your app won't be able to access
+the library's resources, and it won't run properly.
+If you're using Android Studio, this is the string to add to the
+{@code dependency} section of your application's {@code build.gradle} file:
+
+dependencies {
+ compile: "com.google.android.gms:play-services:3.1.+"
+}
+
+
+
+Step 2: Edit Your Application's Manifest
+
+Add the following to your application's manifest:
+
+ - The
com.google.android.c2dm.permission.RECEIVE permission so
+the Android application can register and receive messages.
+ - The
android.permission.INTERNET permission so the Android
+application can send the registration ID to the 3rd party server.
+ - The
android.permission.GET_ACCOUNTS permission as GCM requires
+a Google account (necessary only if if the device is running a version lower than
+Android 4.0.4)
+ - The
android.permission.WAKE_LOCK permission so the application
+can keep the processor from sleeping when a message is received. Optional—use
+only if the app wants to keep the device from sleeping.
+ - An
applicationPackage + ".permission.C2D_MESSAGE"
+permission to prevent other Android applications from registering and receiving
+the Android application's messages. The permission name must exactly match this
+pattern—otherwise the Android application will not receive the messages.
+ - A receiver for
com.google.android.c2dm.intent.RECEIVE , with
+the category set
+as applicationPackage . The receiver should require the
+com.google.android.c2dm.SEND permission, so that only the GCM
+Framework can send a message to it. If your app uses an {@link android.app.IntentService}
+(not required, but a common pattern), this receiver should be an instance of
+{@link android.support.v4.content.WakefulBroadcastReceiver}.
+A {@link android.support.v4.content.WakefulBroadcastReceiver} takes care of
+creating and managing a
+
+partial wake lock for your app.
+
+- A {@link android.app.Service} (typically an {@link android.app.IntentService})
+to which the {@link android.support.v4.content.WakefulBroadcastReceiver} passes off
+the work of handling the GCM message, while ensuring that the device does not
+go back to sleep in the process. Including an {@link android.app.IntentService} is
+optional—you could choose to process your messages in a regular
+{@link android.content.BroadcastReceiver} instead, but realistically, most apps will
+use a {@link android.app.IntentService}.
+
+ - If the GCM feature is critical to the Android application's function, be sure to
+set
android:minSdkVersion="8" or higher in the manifest. This
+ensures that the Android application cannot be installed in an environment in which it
+could not run properly.
+
+
+Here are excerpts from a sample manifest that supports GCM:
+
+
+<manifest package="com.example.gcm" ...>
+
+ <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
+
+ <permission android:name="com.example.gcm.permission.C2D_MESSAGE"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
+
+ <application ...>
+ <receiver
+ android:name=".GcmBroadcastReceiver"
+ android:permission="com.google.android.c2dm.permission.SEND" >
+ <intent-filter>
+ <action android:name="com.google.android.c2dm.intent.RECEIVE" />
+ <category android:name="com.example.gcm" />
+ </intent-filter>
+ </receiver>
+ <service android:name=".GcmIntentService" />
+ </application>
+
+</manifest>
+
+
+ Step 3: Write Your Application
+
+Finally, write your application. This section features a sample client
+application that illustrates how to use the
+
+{@code GoogleCloudMessaging} APIs. The sample consists of a main activity
+({@code DemoActivity}), a {@link android.support.v4.content.WakefulBroadcastReceiver}
+({@code GcmBroadcastReceiver}), and an {@link android.app.IntentService}
+({@code GcmIntentService}). You can find the complete source code for this sample at the
+open source site.
+
+Note the following:
+
+
+ - Among other things, the sample illustrates registration and upstream
+(device-to-cloud) messaging. Upstream messaging only applies to apps that are running against a
+CCS (XMPP) server; HTTP-based servers don't support upstream messaging.
+ - The
+ {@code GoogleCloudMessaging}
+registration APIs replace the old registration process, which was based on the
+now-obsolete client helper library. While the old registration process still works,
+we encourage you to use the newer
+
+{@code GoogleCloudMessaging}
+registration APIs, regardless of your underlying server.
+
+
+Check for Google Play Services APK
+
+As described in
+Setup Google Play Services SDK, apps that rely on the Play Services SDK
+should always check the device for a compatible Google Play services APK before
+accessing Google Play services features. In the sample app this check is done in
+two places: in the main activity's {@code onCreate()} method, and in its
+{@code onResume()} method. The check in {@code onCreate()} ensures that the app
+can't be used without a successful check. The check in {@code onResume()} ensures
+that if the user returns to the running app through some other means, such as
+through the back button, the check is still performed. If the
+device doesn't have a compatible Google Play services APK, your app can call
+{@code GooglePlayServicesUtil.getErrorDialog()} to allow users to download the
+APK from the Google Play Store or enable it in the device's system settings.
+For example:
+
+private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
+...
+@Override
+public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main);
+ mDisplay = (TextView) findViewById(R.id.display);
+
+ context = getApplicationContext();
+
+ // Check device for Play Services APK.
+ if (checkPlayServices()) {
+ // If this check succeeds, proceed with normal processing.
+ // Otherwise, prompt user to get valid Play Services APK.
+ ...
+ }
+}
+
+// You need to do the Play Services APK check here too.
+@Override
+protected void onResume() {
+ super.onResume();
+ checkPlayServices();
+}
+
+/**
+ * Check the device to make sure it has the Google Play Services APK. If
+ * it doesn't, display a dialog that allows users to download the APK from
+ * the Google Play Store or enable it in the device's system settings.
+ */
+private boolean checkPlayServices() {
+ int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
+ if (resultCode != ConnectionResult.SUCCESS) {
+ if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
+ GooglePlayServicesUtil.getErrorDialog(resultCode, this,
+ PLAY_SERVICES_RESOLUTION_REQUEST).show();
+ } else {
+ Log.i(TAG, "This device is not supported.");
+ finish();
+ }
+ return false;
+ }
+ return true;
+}
+
+Register for GCM
+An Android application needs to register with GCM servers before it can receive
+messages. When an app registers, it receives a registration ID, which it can then
+store for future use. In the following snippet the {@code onCreate()} method in the sample app's
+main activity checks to see if the app is already registered with GCM and with
+the server:
+
+/**
+ * Main UI for the demo app.
+ */
+public class DemoActivity extends Activity {
+
+ public static final String EXTRA_MESSAGE = "message";
+ public static final String PROPERTY_REG_ID = "registration_id";
+ private static final String PROPERTY_APP_VERSION = "appVersion";
+ private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
+
+ /**
+ * Substitute you own sender ID here. This is the project number you got
+ * from the API Console, as described in "Getting Started."
+ */
+ String SENDER_ID = "Your-Sender-ID";
+
+ /**
+ * Tag used on log messages.
+ */
+ static final String TAG = "GCMDemo";
+
+ TextView mDisplay;
+ GoogleCloudMessaging gcm;
+ AtomicInteger msgId = new AtomicInteger();
+ SharedPreferences prefs;
+ Context context;
+
+ String regid;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
-A full GCM implementation requires both a client implementation and a server-side implementation. For a step-by-step guide to creating a complete sample implementation that includes both client and server, see Getting Started.
+ setContentView(R.layout.main);
+ mDisplay = (TextView) findViewById(R.id.display);
-
+ context = getApplicationContext();
+
+ // Check device for Play Services APK. If check succeeds, proceed with
+ // GCM registration.
+ if (checkPlayServices()) {
+ gcm = GoogleCloudMessaging.getInstance(this);
+ regid = getRegistrationId(context);
+
+ if (regid.isEmpty()) {
+ registerInBackground();
+ }
+ } else {
+ Log.i(TAG, "No valid Google Play Services APK found.");
+ }
+ }
+...
+}
+
+The app calls {@code getRegistrationId()} to see whether there is an existing
+registration ID stored in shared preferences:
+
+/**
+ * Gets the current registration ID for application on GCM service.
+ * <p>
+ * If result is empty, the app needs to register.
+ *
+ * @return registration ID, or empty string if there is no existing
+ * registration ID.
+ */
+private String getRegistrationId(Context context) {
+ final SharedPreferences prefs = getGCMPreferences(context);
+ String registrationId = prefs.getString(PROPERTY_REG_ID, "");
+ if (registrationId.isEmpty()) {
+ Log.i(TAG, "Registration not found.");
+ return "";
+ }
+ // Check if app was updated; if so, it must clear the registration ID
+ // since the existing regID is not guaranteed to work with the new
+ // app version.
+ int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
+ int currentVersion = getAppVersion(context);
+ if (registeredVersion != currentVersion) {
+ Log.i(TAG, "App version changed.");
+ return "";
+ }
+ return registrationId;
+}
+...
+/**
+ * @return Application's {@code SharedPreferences}.
+ */
+private SharedPreferences getGCMPreferences(Context context) {
+ // This sample app persists the registration ID in shared preferences, but
+ // how you store the regID in your app is up to you.
+ return getSharedPreferences(DemoActivity.class.getSimpleName(),
+ Context.MODE_PRIVATE);
+}
+
+If the registration ID doesn't exist or the app was updated,
+{@code getRegistrationId()} returns an empty string
+to indicate that the app needs to get a new regID. {@code getRegistrationId()} calls
+the following method to check the app version:
+
+/**
+ * @return Application's version code from the {@code PackageManager}.
+ */
+private static int getAppVersion(Context context) {
+ try {
+ PackageInfo packageInfo = context.getPackageManager()
+ .getPackageInfo(context.getPackageName(), 0);
+ return packageInfo.versionCode;
+ } catch (NameNotFoundException e) {
+ // should never happen
+ throw new RuntimeException("Could not get package name: " + e);
+ }
+}
+
+
+If there isn't a valid existing registration ID, {@code DemoActivity} calls the
+following {@code registerInBackground()} method to register. Note that because the GCM
+methods {@code register()} and {@code unregister()} are blocking, this has to
+take place on a background thread. This sample uses {@link android.os.AsyncTask}
+to accomplish this:
+
+
+/**
+ * Registers the application with GCM servers asynchronously.
+ * <p>
+ * Stores the registration ID and app versionCode in the application's
+ * shared preferences.
+ */
+private void registerInBackground() {
+ new AsyncTask() {
+ @Override
+ protected String doInBackground(Void... params) {
+ String msg = "";
+ try {
+ if (gcm == null) {
+ gcm = GoogleCloudMessaging.getInstance(context);
+ }
+ regid = gcm.register(SENDER_ID);
+ msg = "Device registered, registration ID=" + regid;
+
+ // You should send the registration ID to your server over HTTP,
+ // so it can use GCM/HTTP or CCS to send messages to your app.
+ // The request to your server should be authenticated if your app
+ // is using accounts.
+ sendRegistrationIdToBackend();
+
+ // For this demo: we don't need to send it because the device
+ // will send upstream messages to a server that echo back the
+ // message using the 'from' address in the message.
+
+ // Persist the regID - no need to register again.
+ storeRegistrationId(context, regid);
+ } catch (IOException ex) {
+ msg = "Error :" + ex.getMessage();
+ // If there is an error, don't just keep trying to register.
+ // Require the user to click a button again, or perform
+ // exponential back-off.
+ }
+ return msg;
+ }
+
+ @Override
+ protected void onPostExecute(String msg) {
+ mDisplay.append(msg + "\n");
+ }
+ }.execute(null, null, null);
+ ...
+ /**
+ * Sends the registration ID to your server over HTTP, so it can use GCM/HTTP
+ * or CCS to send messages to your app. Not needed for this demo since the
+ * device sends upstream messages to a server that echoes back the message
+ * using the 'from' address in the message.
+ */
+ private void sendRegistrationIdToBackend() {
+ // Your implementation here.
+ }
+}
+
+After registering, the app calls {@code storeRegistrationId()} to store the
+registration ID in shared preferences for future use. This is just one way of
+persisting a regID. You might choose to use a different approach in your app:
+
+/**
+ * Stores the registration ID and app versionCode in the application's
+ * {@code SharedPreferences}.
+ *
+ * @param context application's context.
+ * @param regId registration ID
+ */
+private void storeRegistrationId(Context context, String regId) {
+ final SharedPreferences prefs = getGCMPreferences(context);
+ int appVersion = getAppVersion(context);
+ Log.i(TAG, "Saving regId on app version " + appVersion);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString(PROPERTY_REG_ID, regId);
+ editor.putInt(PROPERTY_APP_VERSION, appVersion);
+ editor.commit();
+}
+
+Send a message
+When the user clicks the app's Send button, the app sends an
+upstream message using the
+
+{@code GoogleCloudMessaging} APIs. In order to receive the upstream message,
+your server should be connected to CCS. You can use one of the demo servers in
+Implementing an XMPP-based App Server to run the sample and connect
+to CCS.
+
+public void onClick(final View view) {
+ if (view == findViewById(R.id.send)) {
+ new AsyncTask() {
+ @Override
+ protected String doInBackground(Void... params) {
+ String msg = "";
+ try {
+ Bundle data = new Bundle();
+ data.putString("my_message", "Hello World");
+ data.putString("my_action",
+ "com.google.android.gcm.demo.app.ECHO_NOW");
+ String id = Integer.toString(msgId.incrementAndGet());
+ gcm.send(SENDER_ID + "@gcm.googleapis.com", id, data);
+ msg = "Sent message";
+ } catch (IOException ex) {
+ msg = "Error :" + ex.getMessage();
+ }
+ return msg;
+ }
+
+ @Override
+ protected void onPostExecute(String msg) {
+ mDisplay.append(msg + "\n");
+ }
+ }.execute(null, null, null);
+ } else if (view == findViewById(R.id.clear)) {
+ mDisplay.setText("");
+ }
+}
+
+Receive a message
+
+As described above in Step 2, the app includes a
+{@link android.support.v4.content.WakefulBroadcastReceiver} for the com.google.android.c2dm.intent.RECEIVE
+intent. A broadcast receiver is the mechanism GCM uses to deliver messages. When {@code onClick()}
+calls {@code gcm.send()}, it triggers the broadcast receiver's {@code onReceive()}
+method, which has the responsibility of making sure that the GCM message gets handled.
+A {@link android.support.v4.content.WakefulBroadcastReceiver} is a special type of
+broadcast receiver that takes care of
+creating and managing a
+
+partial wake lock for your app.
+It passes off the work of processing the GCM message to a
+{@link android.app.Service} (typically an
+{@link android.app.IntentService}), while ensuring that the device does not
+go back to sleep in the transition. If you don't hold a wake lock while transitioning
+the work to a service, you are effectively allowing the device to go back to sleep before
+the work completes. The net result is that the app might not finish processing
+the GCM message until some arbitrary point in the future, which is not what you want.
+
+Note: Using {@link android.support.v4.content.WakefulBroadcastReceiver}
+is not a requirement. If you have a relatively simple app that doesn't require
+a service, you can intercept the GCM message in a regular {@link android.content.BroadcastReceiver}
+and do your processing there. Once you get the intent that GCM passes into
+your broadcast receiver's {@code onReceive()} method, what you do with it
+is up to you.
+
+This snippet starts {@code GcmIntentService} with the method
+{@link android.support.v4.content.WakefulBroadcastReceiver#startWakefulService startWakefulService()}.
+This method is comparable to {@link android.content.Context#startService startService()}, except that
+the {@link android.support.v4.content.WakefulBroadcastReceiver} is holding a
+wake lock when the service starts. The intent that is passed with
+{@link android.support.v4.content.WakefulBroadcastReceiver#startWakefulService startWakefulService()}
+holds an extra identifying the wake lock:
+
+
+public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Explicitly specify that GcmIntentService will handle the intent.
+ ComponentName comp = new ComponentName(context.getPackageName(),
+ GcmIntentService.class.getName());
+ // Start the service, keeping the device awake while it is launching.
+ startWakefulService(context, (intent.setComponent(comp)));
+ setResultCode(Activity.RESULT_OK);
+ }
+}
+
+The intent service shown below does the actual work of handling the GCM
+message. When the service is finished, it calls
+{@link android.support.v4.content.WakefulBroadcastReceiver#completeWakefulIntent GcmBroadcastReceiver.completeWakefulIntent()}
+to release the wake lock. The
+{@link android.support.v4.content.WakefulBroadcastReceiver#completeWakefulIntent completeWakefulIntent()}
+method has as its parameter the same intent that was
+passed in from the {@link android.support.v4.content.WakefulBroadcastReceiver}.
+
+
+This snippet processes the GCM message based on message type, and posts the
+result in a notification. But what you do with GCM messages in your app is up to
+you—the possibilities are endless. For example, the message might be a ping,
+telling the app to sync to a server to retrieve new content, or it might be a
+chat message that you display in the UI.
+
+
+public class GcmIntentService extends IntentService {
+ public static final int NOTIFICATION_ID = 1;
+ private NotificationManager mNotificationManager;
+ NotificationCompat.Builder builder;
+
+ public GcmIntentService() {
+ super("GcmIntentService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Bundle extras = intent.getExtras();
+ GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
+ // The getMessageType() intent parameter must be the intent you received
+ // in your BroadcastReceiver.
+ String messageType = gcm.getMessageType(intent);
+
+ if (!extras.isEmpty()) { // has effect of unparcelling Bundle
+ /*
+ * Filter messages based on message type. Since it is likely that GCM
+ * will be extended in the future with new message types, just ignore
+ * any message types you're not interested in, or that you don't
+ * recognize.
+ */
+ if (GoogleCloudMessaging.
+ MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
+ sendNotification("Send error: " + extras.toString());
+ } else if (GoogleCloudMessaging.
+ MESSAGE_TYPE_DELETED.equals(messageType)) {
+ sendNotification("Deleted messages on server: " +
+ extras.toString());
+ // If it's a regular GCM message, do some work.
+ } else if (GoogleCloudMessaging.
+ MESSAGE_TYPE_MESSAGE.equals(messageType)) {
+ // This loop represents the service doing some work.
+ for (int i=0; i<5; i++) {
+ Log.i(TAG, "Working... " + (i+1)
+ + "/5 @ " + SystemClock.elapsedRealtime());
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException e) {
+ }
+ }
+ Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());
+ // Post notification of received message.
+ sendNotification("Received: " + extras.toString());
+ Log.i(TAG, "Received: " + extras.toString());
+ }
+ }
+ // Release the wake lock provided by the WakefulBroadcastReceiver.
+ GcmBroadcastReceiver.completeWakefulIntent(intent);
+ }
+
+ // Put the message into a notification and post it.
+ // This is just one simple example of what you might choose to do with
+ // a GCM message.
+ private void sendNotification(String msg) {
+ mNotificationManager = (NotificationManager)
+ this.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
+ new Intent(this, DemoActivity.class), 0);
+
+ NotificationCompat.Builder mBuilder =
+ new NotificationCompat.Builder(this)
+ .setSmallIcon(R.drawable.ic_stat_gcm)
+ .setContentTitle("GCM Notification")
+ .setStyle(new NotificationCompat.BigTextStyle()
+ .bigText(msg))
+ .setContentText(msg);
+
+ mBuilder.setContentIntent(contentIntent);
+ mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
+ }
+}
+
+Running the Sample
+
+To run the sample:
+
+
+ - Follow the instructions in Getting Started to get your sender ID and
+ API key.
+ - Implement your client app, as described in this document. You can find the complete source
+ code for the client app at the open source site.
+ - Run one of the demo servers (Java or Python) provided in
+Implementing an XMPP-based App Server. Whichever demo server you
+ choose, don't forget to edit its code before running it to supply
+your sender ID and API key.
+
+
+
+
+Viewing Statistics
+
+To view statistics and any error messages for your GCM applications:
+
+ - Go to the
Developer Console .
+ - Login with your developer account.
+
You will see a page that has a list of all of your apps.
+ - Click on the "statistics" link next to the app for which you
+want to view GCM stats.
+
Now you are on the statistics page.
+ - Go to the drop-down menu and select the GCM metric you want to view.
+
+
+Note: Stats on the Google API Console are not
+enabled for GCM. You must use the Developer Console.
diff --git a/docs/html/google/gcm/gcm.jd b/docs/html/google/gcm/gcm.jd
index ceb82b03702b0178e524cea9f3b6f508a0059b32..3c80b5f9ae5f1a7aae54165f91686eccb3a37448 100644
--- a/docs/html/google/gcm/gcm.jd
+++ b/docs/html/google/gcm/gcm.jd
@@ -1,48 +1,23 @@
-page.title=GCM Architectural Overview
+page.title=Overview
@jd:body
@@ -50,24 +25,15 @@ page.title=GCM Architectural Overview
developers send data from servers to their Android applications on Android
devices, and upstream messages from the user's device back to the cloud.
This could be a lightweight message telling the Android application
-that there is new data to be fetched from the server (for instance, a movie
-uploaded by a friend), or it could be a message containing up to 4kb of payload
+that there is new data to be fetched from the server (for instance, a "new email"
+notification informing the application that it is out of sync with the back end),
+or it could be a message containing up to 4kb of payload
data (so apps like instant messaging can consume the message directly). The GCM
service handles all aspects of queueing of messages and delivery to the target
Android application running on the target device.
-
-GCM introduces GCM Cloud Connection Server (CCS), which you can use
-in tandem with GCM HTTP service/endpoint/APIs.
-CCS uses XMPP, and it offers asynchronous, bidirectional
-messaging. For more information, see
-GCM Cloud Connection Server.
-
To jump right into using GCM with your Android
- applications, see the instructions in Getting Started.
-
-
-Introduction
+ applications, see Getting Started.
Here are the primary characteristics of Google Cloud
Messaging (GCM):
@@ -75,9 +41,11 @@ Messaging (GCM):
- It allows 3rd-party application servers to send messages to
their Android applications.
- - Using the GCM Cloud Connection Server, you can receive upstream messages from the user's device.
+ - Using the GCM Cloud Connection Server, you can receive
+upstream messages from the user's device.
- An Android application on an Android device doesn't need to be running to receive
-messages. The system will wake up the Android application via Intent broadcast when the message arrives, as long as the application is set up with the proper
+messages. The system will wake up the Android application via Intent broadcast
+when the message arrives, as long as the application is set up with the proper
broadcast receiver and permissions.
- It does not provide any built-in user interface or other handling for
message data. GCM simply passes raw message data received straight to the
@@ -85,57 +53,67 @@ Android application, which has full control of how to handle it. For example, t
application might post a notification, display a custom user interface, or
silently sync data.
- It requires devices running Android 2.2 or higher that also have the
-Google Play Store application installed, or or an emulator running Android 2.2 with Google APIs. However, you are not limited to deploying your
+Google Play Store application installed, or or an emulator running Android 2.2
+with Google APIs. However, you are not limited to deploying your
Android applications through Google Play Store.
- - It uses an existing connection for Google services. For pre-3.0 devices, this requires users to
-set up their Google account on their mobile devices. A Google account is not a requirement on devices running Android 4.0.4 or higher.
+ - It uses an existing connection for Google services. For pre-3.0 devices,
+this requires users to
+set up their Google account on their mobile devices. A Google account is not a
+requirement on devices running Android 4.0.4 or higher.
-Architectural Overview
-This section gives an overview of how GCM works.
+
+Key Concepts
+
This table summarizes the key terms and concepts involved in GCM. It is
divided into these categories:
- - Components — The physical entities that play a role in
+
- Components — The entities that play a primary role in
GCM.
- Credentials — The IDs and tokens that are used in
different stages of GCM to ensure that all parties have been authenticated, and
that the message is going to the correct place.
+
+ Table 1. GCM components and credentials.
+
Components |
- Mobile Device |
- The device that is running an Android application that uses
-GCM. This must be a 2.2 Android device that has Google Play Store installed, and it must
-have at least one logged in Google account if the device is running a version lower than Android 4.0.4. Alternatively, for testing you can use an emulator running Android 2.2 with Google APIs. |
+ Client App |
+ The GCM-enabled Android application that is running on a
+ device. This must be a 2.2 Android device that has Google Play Store installed, and it must
+have at least one logged in Google account if the device is running a version
+lower than Android 4.0.4. Alternatively, for testing you can use an emulator
+running Android 2.2 with Google APIs. |
3rd-party Application Server |
- An application server that developers set up as part of implementing
-GCM in their applications. The 3rd-party application server sends data to an
-Android application on the device via the GCM server. |
+ An application server that you write as part of implementing
+GCM. The 3rd-party application server sends data to an
+Android application on the device via the GCM connection server. |
- GCM Servers |
- The Google servers involved in taking messages from the 3rd-party
+ | GCM Connection Servers |
+ The Google-provided servers involved in taking messages from the 3rd-party
application server and sending them to the device. |
- Credentials |
+ Credentials |
Sender ID |
- A project number you acquire from the API console, as described in Getting Started. The sender
-ID is used in the registration process to identify an
-Android application that is permitted to send messages to the device. |
+ A project number you acquire from the API console, as described in
+Getting Started. The sender
+ID is used in the registration process to identify a
+3rd-party application server that is permitted to send messages to the device. |
Application ID |
The Android application that is registering to receive messages. The Android application
-is identified by the package name from the manifest.
+is identified by the package name from the manifest.
This ensures that the messages are targeted to the correct Android application. |
@@ -158,7 +136,8 @@ which would cause delivery errors.
Google User Account |
- For GCM to work, the mobile device must include at least one Google account if the device is running a version lower than Android 4.0.4. |
+ For GCM to work, the mobile device must include at least one Google
+account if the device is running a version lower than Android 4.0.4. |
Sender Auth Token |
@@ -167,25 +146,46 @@ server that gives the application server authorized access to Google services.
The API key is included in the header of POST requests that send messages.
-
- Notification Key |
- Part of the user notifications feature, which provides a mapping between a user and instances of an app running on multiple devices owned by the user. The {@code notification_key} is the token that GCM uses to fan out notifications to all devices whose registration IDs are associated with the key. For more discussion of this topic, see User Notifications. |
-
+
-
- Notification Key Name |
- Part of the user notifications feature. The {@code notification_key_name} is a name or identifier (can be a username for a 3rd-party app) that is unique to a given user. It is used by third parties to group together registration IDs for a single user. For more discussion of this topic, see User Notifications. |
-
+Architectural Overview
-
+ A GCM implementation includes a Google-provided
+connection server, a 3rd-party app server that interacts with the connection
+server, and a GCM-enabled client app running on an Android device:
- Lifecycle Flow
+
+
+
+ Figure 1. GCM Architecture.
+
+
+ This is how these components interact:
+
+ - Google-provided GCM Connection Servers take messages from
+a 3rd-party application server and send these messages to a
+GCM-enabled Android application (the "client app") running on a device.
+Currently Google provides connection servers for HTTP
+and XMPP.
+ - The 3rd-Party Application Server is a component that you
+implement to work with your chosen GCM connection server(s). App servers send
+messages to a GCM connection server; the connection server enqueues and stores the
+message, and then sends it to the device when the device is online.
+For more information, see Implementing GCM Server.
+ - The Client App is a GCM-enabled Android application running
+on a device. To receive GCM messages, this app must register with GCM and get a
+registration ID. If you are using the XMPP (CCS) connection
+server, the client app can send "upstream" messages back to the connection server.
+For more information on how to implement the client app, see
+Implementing GCM Client.
+
+
+ Lifecycle Flow
- Enable GCM. An Android application running on a
mobile device registers to receive messages.
- - User Notifications. A 3rd-party server can optionally group multiple registration IDs
-in a {@code notification_key} to send messages to multiple devices owned by a single user.
+
- Send a message. A 3rd-party application
server sends messages to the device.
- Receive a message. An Android application
@@ -194,62 +194,18 @@ receives a message from a GCM server.
These processes are described in more detail below.
-Enable GCM
-
-This is the sequence of events that occurs when an Android application
-running on a mobile device registers to receive messages:
+Enable GCM
-
- - The first time the Android application needs to use the messaging service, it
-fires off a registration Intent to a GCM server.
-
This registration Intent
-(com.google.android.c2dm.intent.REGISTER ) includes the sender ID, and the Android application ID.
-Note: Because there is no lifecycle method that is called when the application is run for
-the first time, the registration intent should be sent on onCreate() , but only if the application is not registered yet.
+ The first time the Android application needs to use the messaging service, it
+calls the
+{@code GoogleCloudMessaging} method {@code register()}, as discussed in
+Implementing GCM Client.
+The {@code register()} method returns a registration ID. The Android
+application should store this ID for later use (for instance,
+to check in onCreate() if it is already registered).
-
- - If the registration is successful, the GCM server broadcasts a
com.google.android.c2dm.intent.REGISTRATION intent which gives the Android application a registration
-ID.
- The Android application should store this ID for later use (for instance, to check on onCreate() if it is already registered).
-Note that Google may periodically refresh the registration ID, so you should design your Android application
-with the understanding that the com.google.android.c2dm.intent.REGISTRATION intent may be called
-multiple times. Your Android application needs to be able to respond
-accordingly.
- - To complete the registration, the Android application sends the registration ID to
-the application server. The application server typically stores the registration
-ID in a database.
-
-
-The registration ID lasts until the Android application explicitly unregisters
-itself, or until Google refreshes the registration ID for your Android application.
-
-Note: When users uninstall an application, it is not automatically unregistered on GCM. It is only unregistered when the GCM server tries to send a message to the device and the device answers that the application is uninstalled or it does not have a broadcast receiver configured to receive com.google.android.c2dm.intent.RECEIVE intents. At that point, your server should mark the device as unregistered (the server will receive a NotRegistered error).
-
-Note that it might take a few minutes for the registration ID to be completely removed from the GCM server. So if the 3rd-party server sends a message during this time, it will get a valid message ID, even though the message will not be delivered to the device.
-
-
-
-Send a Message
-
-For an application server to send a message to an Android application, the following things must be in
-place:
-
-
- - The Android application has stored a target that it can specify as the recipient of a message. This can be one of the following:
-
- - A single registration ID (or an array of registration IDs) that allows the app to receive messages
-for a particular device.
- - A {@code notification_key} and corresponding {@code notification_key_name}, used to map a single user to multiple registration IDs. For more discussion of this topic, see User Notifications.
-
-
-
-- An API key. This is something that the developer must have already
-set up on the application server for the Android application (for more discussion, see
-Role of the 3rd-party Application Server). Now it will
-get used to send messages to the device.
-
+Send a message
Here is the sequence of events that occurs when the application server sends a
message:
@@ -264,13 +220,14 @@ Android application via Intent broadcast with proper permissions, so that only t
targeted Android application gets the message. This wakes the Android application up. The
Android application does not need to be running beforehand to receive the message.
- The Android application processes the message. If the Android application is doing
-non-trivial processing, you may want to grab a {@link android.os.PowerManager.WakeLock} and do any processing in a Service.
+non-trivial processing, you may want to grab a
+{@link android.os.PowerManager.WakeLock} and do any processing in a service.
An Android application can unregister GCM if it no longer wants to receive
messages.
-Receive a Message
+Receive a message
This is the sequence of events that occurs when an Android application
installed on a mobile device receives a message:
@@ -282,482 +239,8 @@ pairs from the message payload, if any.
in a com.google.android.c2dm.intent.RECEIVE Intent as a set of
extras.
- The Android application extracts the raw data
-from the
com.google.android.c2dm.intent.RECEIVE Intent by key and processes the data.
-
-
-What Does the User See?
-
-When mobile device users install Android applications that include GCM, the Google Play Store will inform them that the Android application
-includes GCM. They must approve the use of this feature to install the
-Android application.
-
-
-Role of the 3rd-party Application Server
-
-Before you can write client Android applications that use the GCM feature, you must
-have an application server that meets the following criteria:
-
-
- - Able to communicate with your client.
- - Able to fire off HTTPS requests to the GCM server.
- - Able to handle requests and resend them as needed, using exponential back-off.
- - Able to store the API key and client registration IDs. The
-API key is included in the header of POST requests that send
-messages.
-
-
-Sending Messages
-This section describes how the 3rd-party application server sends messages to one or more mobile devices. Note the following:
-
- - A 3rd-party application server can either send messages to a single device or to multiple devices. A message sent to multiple devices simultaneously is called a multicast message.
- - To send a single message to multiple devices owned by a single user, you can use a {@code notification_key}, as described in User Notifications.
-
-
- You have 2 choices in how you construct requests and responses: plain text or JSON.
- - However, to send multicast messages, you must use JSON. Plain text will not work.
-
-Before the 3rd-party application server can send a message to an
- Android application, it must have received a registration ID from it.
-Request format
-To send a message, the application server issues a POST request to https://android.googleapis.com/gcm/send .
-A message request is made of 2 parts: HTTP header and HTTP body.
-
-The HTTP header must contain the following headers:
-
- Authorization : key=YOUR_API_KEY
- Content-Type : application/json for JSON; application/x-www-form-urlencoded;charset=UTF-8 for plain text.
-
-
-
-For example:
-
- Content-Type:application/json
-Authorization:key=AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA
-
-{
- "registration_ids" : ["APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx..."],
- "data" : {
- ...
- },
-}
-
- Note: If Content-Type is omitted, the format is assumed to be plain text.
-
-
- The HTTP body content depends on whether you're using JSON or plain text. For JSON, it must contain a string representing a JSON object with the following fields:
-
-
- Field |
- Description |
-
-
- registration_ids |
- A string array with the list of devices (registration IDs) receiving the message. It must contain at least 1 and at most 1000 registration IDs. To send a multicast message, you must use JSON. For sending a single message to a single device, you could use a JSON object with just 1 registration id, or plain text (see below). A request must include a recipient—this can be either a registration ID, an array of registration IDs, or a {@code notification_key}. |
-
-
- notification_key |
- A string that maps a single user to multiple registration IDs associated with that user. This
-allows a 3rd-party server to send a single message to multiple app instances (typically on multiple devices) owned by a single user. A 3rd-party server can use {@code notification_key} as the target for a message instead of an individual registration ID (or array of registration IDs). The maximum number of members allowed for a {@code notification_key} is 10. For more discussion of this topic, see User Notifications. Optional. |
-
-
-
- notification_key_name |
- A name or identifier (can be a username for a 3rd-party app) that is unique to a given user. It is used by 3rd parties to group together registration IDs for a single user. The notification_key_name should be uniquely named per app in case you have multiple apps for the same project ID. This ensures that notifications only go to the intended target app. For more discussion of this topic, see User Notifications. |
-
-
-
- collapse_key |
- An arbitrary string (such as "Updates Available") that is used to collapse a group of like messages
-when the device is offline, so that only the last message gets sent to the
-client. This is intended to avoid sending too many messages to the phone when it
-comes back online. Note that since there is no guarantee of the order in which
-messages get sent, the "last" message may not actually be the last
-message sent by the application server. See Advanced Topics for more discussion of this topic. Optional. |
-
-
- data |
- A JSON object whose fields represents the key-value pairs of the message's payload data. If present, the payload data it will be
-included in the Intent as application data, with the key being the extra's name. For instance, "data":{"score":"3x1"} would result in an intent extra named score whose value is the string 3x1 .
-
-There is no limit on the number of key/value pairs, though there is a limit on the total size of the message (4kb). The values could be any JSON object, but we recommend using strings, since the values will be converted to strings in the GCM server anyway. If you want to include objects or other non-string data types (such as integers or booleans), you have to do the conversion to string yourself. Also note that the key cannot be a reserved word (from or any word starting with google. ). To complicate things slightly, there are some reserved words (such as collapse_key ) that are technically allowed in payload data. However, if the request also contains the word, the value in the request will overwrite the value in the payload data. Hence using words that are defined as field names in this table is not recommended, even in cases where they are technically allowed. Optional. |
-
-
-
-
- delay_while_idle |
- If included, indicates that the message should not be sent immediately
-if the device is idle. The server will wait for the device to become active, and
-then only the last message for each collapse_key value will be
-sent. Optional. The default value is false , and must be a JSON boolean. |
-
-
- time_to_live |
- How long (in seconds) the message should be kept on GCM storage if the device is offline. Optional (default time-to-live is 4 weeks, and must be set as a JSON number). |
-
-
- restricted_package_name |
- A string containing the package name of your application. When set, messages will only be sent to registration IDs that match the package name. Optional.
- |
-
-
- dry_run |
- If included, allows developers to test their request without actually sending a message. Optional. The default value is false , and must be a JSON boolean.
- |
-
-
-
- If you are using plain text instead of JSON, the message fields must be set as HTTP parameters sent in the body, and their syntax is slightly different, as described below:
-
-
- Field |
- Description |
-
-
- registration_id |
- Must contain the registration ID of the single device receiving the message. Required. |
-
-
- collapse_key |
- Same as JSON (see previous table). Optional. |
-
-
- data.<key> |
-
- Payload data, expressed as parameters prefixed with data. and suffixed as the key. For instance, a parameter of data.score=3x1 would result in an intent extra named score whose value is the string 3x1 . There is no limit on the number of key/value parameters, though there is a limit on the total size of the message. Also note that the key cannot be a reserved word (from or any word starting with
-google. ). To complicate things slightly, there are some reserved words (such as collapse_key ) that are technically allowed in payload data. However, if the request also contains the word, the value in the request will overwrite the value in the payload data. Hence using words that are defined as field names in this table is not recommended, even in cases where they are technically allowed. Optional. |
-
-
-
- delay_while_idle |
- Should be represented as 1 or true for true , anything else for false . Optional. The default value is false . |
-
-
- time_to_live |
- Same as JSON (see previous table). Optional. |
-
-
- restricted_package_name |
- Same as JSON (see previous table). Optional.
- |
-
-
- dry_run |
- Same as JSON (see previous table). Optional.
- |
-
-
-
- If you want to test your request (either JSON or plain text) without delivering the message to the devices, you can set an optional HTTP or JSON parameter called dry_run with the value true . The result will be almost identical to running the request without this parameter, except that the message will not be delivered to the devices. Consequently, the response will contain fake IDs for the message and multicast fields (see Response format).
-
- Example requests
- Here is the smallest possible request (a message without any parameters and just one recipient) using JSON:
- { "registration_ids": [ "42" ] }
-
- And here the same example using plain text:
- registration_id=42
-
- Here is a message with a payload and 6 recipients:
- { "data": {
- "score": "5x1",
- "time": "15:10"
- },
- "registration_ids": ["4", "8", "15", "16", "23", "42"]
-}
- Here is a message with all optional fields set and 6 recipients:
- { "collapse_key": "score_update",
- "time_to_live": 108,
- "delay_while_idle": true,
- "data": {
- "score": "4x8",
- "time": "15:16.2342"
- },
- "registration_ids":["4", "8", "15", "16", "23", "42"]
-}
- And here is the same message using plain-text format (but just 1 recipient):
-
- collapse_key=score_update&time_to_live=108&delay_while_idle=1&data.score=4x8&data.time=15:16.2342®istration_id=42
-
-
- Note: If your organization has a firewall
-that restricts the traffic to or
-from the Internet, you need to configure it to allow connectivity with GCM in order for
-your Android devices to receive messages.
-The ports to open are: 5228, 5229, and 5230. GCM typically only uses 5228, but
-it sometimes uses 5229 and 5230. GCM doesn't provide specific IPs, so you should allow
-your firewall to accept outgoing connections to all IP addresses
-contained in the IP blocks listed in Google's ASN of 15169.
-
-
- Response format
-
- There are two possible outcomes when trying to send a message:
-
- - The message is processed successfully.
- - The GCM server rejects the request.
-
-
- When the message is processed successfully, the HTTP response has a 200 status and the body contains more information about the status of the message (including possible errors). When the request is rejected,
-the HTTP response contains a non-200 status code (such as 400, 401, or 503).
-
- The following table summarizes the statuses that the HTTP response header might contain. Click the troubleshoot link for advice on how to deal with each type of error.
-
-
- Response |
- Description |
-
-
- 200 |
- Message was processed successfully. The response body will contain more details about the message status, but its format will depend whether the request was JSON or plain text. See Interpreting a success response for more details. |
-
-
- 400 |
- Only applies for JSON requests. Indicates that the request could not be parsed as JSON, or it contained invalid fields (for instance, passing a string where a number was expected). The exact failure reason is described in the response and the problem should be addressed before the request can be retried. |
-
-
- 401 |
- There was an error authenticating the sender account. Troubleshoot |
-
-
- 5xx |
- Errors in the 500-599 range (such as 500 or 503) indicate that there was an internal error in the GCM server while trying to process the request, or that the server is temporarily unavailable (for example, because of timeouts). Sender must retry later, honoring any Retry-After header included in the response. Application servers must implement exponential back-off. Troubleshoot |
-
-
-
- Interpreting a success response
- When a JSON request is successful (HTTP status code 200), the response body contains a JSON object with the following fields:
-
-
- Field |
- Description |
-
-
- multicast_id |
- Unique ID (number) identifying the multicast message. |
-
-
- success |
- Number of messages that were processed without an error. |
-
-
- failure |
- Number of messages that could not be processed. |
-
-
- canonical_ids |
- Number of results that contain a canonical registration ID. See Advanced Topics for more discussion of this topic. |
-
-
- results |
- Array of objects representing the status of the messages processed. The objects are listed in the same order as the request (i.e., for each registration ID in the request, its result is listed in the same index in the response) and they can have these fields:
-
- message_id : String representing the message when it was successfully processed.
- registration_id : If set, means that GCM processed the message but it has another canonical registration ID for that device, so sender should replace the IDs on future requests (otherwise they might be rejected). This field is never set if there is an error in the request.
-
- error : String describing an error that occurred while processing the message for that recipient. The possible values are the same as documented in the above table, plus "Unavailable" (meaning GCM servers were busy and could not process the message for that particular recipient, so it could be retried).
- |
-
-
- If the value of failure and canonical_ids is 0, it's not necessary to parse the remainder of the response. Otherwise, we recommend that you iterate through the results field and do the following for each object in that list:
-
- - If
message_id is set, check for registration_id :
-
- - If
registration_id is set, replace the original ID with the new value (canonical ID) in your server database. Note that the original ID is not part of the result, so you need to obtain it from the list of registration_ids passed in the request (using the same index).
-
-
- - Otherwise, get the value of
error :
-
- - If it is
Unavailable , you could retry to send it in another request.
- - If it is
NotRegistered , you should remove the registration ID from your server database because the application was uninstalled from the device or it does not have a broadcast receiver configured to receive com.google.android.c2dm.intent.RECEIVE intents.
- - Otherwise, there is something wrong in the registration ID passed in the request; it is probably a non-recoverable error that will also require removing the registration from the server database. See Interpreting an error response for all possible error values.
-
-
-
-
- When a plain-text request is successful (HTTP status code 200), the response body contains 1 or 2 lines in the form of key/value pairs.
-The first line is always available and its content is either id=ID of sent message or Error=GCM error code . The second line, if available,
-has the format of registration_id=canonical ID . The second line is optional, and it can only be sent if the first line is not an error. We recommend handling the plain-text response in a similar way as handling the JSON response:
-
- - If first line starts with
id , check second line:
-
- - If second line starts with
registration_id , gets its value and replace the registration IDs in your server database.
-
-
- - Otherwise, get the value of
Error :
-
- - If it is
NotRegistered , remove the registration ID from your server database.
- - Otherwise, there is probably a non-recoverable error (Note: Plain-text requests will never return
Unavailable as the error code, they would have returned a 500 HTTP status instead).
-
-
-
-
- Interpreting an error response
- Here are the recommendations for handling the different types of error that might occur when trying to send a message to a device:
-
-
-- Missing Registration ID
-- Check that the request contains a registration ID (either in the
registration_id parameter in a plain text message, or in the registration_ids field in JSON).
- Happens when error code is MissingRegistration .
-
-- Invalid Registration ID
-- Check the formatting of the registration ID that you pass to the server. Make sure it matches the registration ID the phone receives in the
com.google.android.c2dm.intent.REGISTRATION intent and that you're not truncating it or adding additional characters.
- Happens when error code is InvalidRegistration .
-
-- Mismatched Sender
-- A registration ID is tied to a certain group of senders. When an application registers for GCM usage, it must specify which senders are allowed to send messages. Make sure you're using one of those when trying to send messages to the device. If you switch to a different sender, the existing registration IDs won't work.
-Happens when error code is
MismatchSenderId .
-
-- Unregistered Device
-- An existing registration ID may cease to be valid in a number of scenarios, including:
-
- - If the application manually unregisters by issuing a
com.google.android.c2dm.intent.UNREGISTER intent.
- - If the application is automatically unregistered, which can happen (but is not guaranteed) if the user uninstalls the application.
- - If the registration ID expires. Google might decide to refresh registration IDs.
- - If the application is updated but the new version does not have a broadcast receiver configured to receive
com.google.android.c2dm.intent.RECEIVE intents.
-
-For all these cases, you should remove this registration ID from the 3rd-party server and stop using it to send
-messages.
- Happens when error code is NotRegistered .
-
-- Message Too Big
- - The total size of the payload data that is included in a message can't exceed 4096 bytes. Note that this includes both the size of the keys as well as the values.
-
Happens when error code is MessageTooBig .
-
-- Invalid Data Key
-- The payload data contains a key (such as
from or any value prefixed by google. ) that is used internally by GCM in the com.google.android.c2dm.intent.RECEIVE Intent and cannot be used. Note that some words (such as collapse_key ) are also used by GCM but are allowed in the payload, in which case the payload value will be overridden by the GCM value.
-
-Happens when the error code is InvalidDataKey .
-
-- Invalid Time To Live
- - The value for the Time to Live field must be an integer representing a duration in seconds between 0 and 2,419,200 (4 weeks). Happens when error code is
InvalidTtl .
-
-
- - Authentication Error
- - The sender account that you're trying to use to send a message couldn't be authenticated. Possible causes are:
-- Authorization header missing or with invalid syntax.
-- Invalid project number sent as key.
-- Key valid but with GCM service disabled.
-- Request originated from a server not whitelisted in the Server Key IPs.
-
-
-Check that the token you're sending inside the Authorization header is the correct API key associated with your project. You can check the validity of your API key by running the following command:
-
-# api_key=YOUR_API_KEY
-
-# curl --header "Authorization: key=$api_key" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"ABC\"]}"
-
-
-
-If you receive a 401 HTTP status code, your API key is not valid. Otherwise you should see something like this:
-
-
-{"multicast_id":6782339717028231855,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
-
-If you want to confirm the validity of a registration ID, you can do so by replacing "ABC" with the registration ID.
-
-Happens when the HTTP status code is 401.
-
- - Timeout
-
-- The server couldn't process the request in time. You should retry the
-same request, but you MUST obey the following requirements:
-
-
-
-- Honor the
Retry-After header if it's included in the response from the GCM server.
-
-
-- Implement exponential back-off in your retry mechanism. This means an
-exponentially increasing delay after each failed retry (e.g. if you waited one
-second before the first retry, wait at least two second before the next one,
-then 4 seconds and so on). If you're sending multiple messages, delay each one
-independently by an additional random amount to avoid issuing a new request for
-all messages at the same time.
-
-
-Senders that cause problems risk being blacklisted.
-
-Happens when the HTTP status code is between 501 and 599, or when the error field of a JSON object in the results array is Unavailable .
-
-
-- Internal Server Error
-
--
-The server encountered an error while trying to process the request. You
-could retry the same request (obeying the requirements listed in the Timeout
-section), but if the error persists, please report the problem in the android-gcm group.
-
-Happens when the HTTP status code is 500, or when the error field of a JSON
-object in the results array is InternalServerError .
-
-
-- Invalid Package Name
-
--
-A message was addressed to a registration ID whose package name did not match the value passed in the request. Happens when error code is
-
InvalidPackageName .
-
-
-
-
- Example responses
- This section shows a few examples of responses indicating messages that were processed successfully. See Example requests for the requests these responses are based on.
- Here is a simple case of a JSON message successfully sent to one recipient without canonical IDs in the response:
- { "multicast_id": 108,
- "success": 1,
- "failure": 0,
- "canonical_ids": 0,
- "results": [
- { "message_id": "1:08" }
- ]
-}
-
- Or if the request was in plain-text format:
- id=1:08
-
-
- Here are JSON results for 6 recipients (IDs 4, 8, 15, 16, 23, and 42 respectively) with 3 messages successfully processed, 1 canonical registration ID returned, and 3 errors:
- { "multicast_id": 216,
- "success": 3,
- "failure": 3,
- "canonical_ids": 1,
- "results": [
- { "message_id": "1:0408" },
- { "error": "Unavailable" },
- { "error": "InvalidRegistration" },
- { "message_id": "1:1516" },
- { "message_id": "1:2342", "registration_id": "32" },
- { "error": "NotRegistered"}
- ]
-}
-
- In this example:
-
- - First message: success, not required.
- - Second message: should be resent (to registration ID 8).
- - Third message: had an unrecoverable error (maybe the value got corrupted in the database).
- - Fourth message: success, nothing required.
- - Fifth message: success, but the registration ID should be updated in the server database (from 23 to 32).
- - Sixth message: registration ID (42) should be removed from the server database because the application was uninstalled from the device.
-
- Or if just the 4th message above was sent using plain-text format:
- Error=InvalidRegistration
-
- If the 5th message above was also sent using plain-text format:
- id=1:2342
-registration_id=32
-
-
- Viewing Statistics
-
- To view statistics and any error messages for your GCM applications:
-
- - Go to the
Developer Console .
- - Login with your developer account.
-
You will see a page that has a list of all of your apps.
- - Click on the "statistics" link next to the app for which you want to view GCM stats.
-
Now you are on the statistics page.
- - Go to the drop-down menu and select the GCM metric you want to view.
-
+from the com.google.android.c2dm.intent.RECEIVE Intent
+by key and processes the data.
- Note: Stats on the Google API Console are not enabled for GCM. You must use the Developer Console.
diff --git a/docs/html/google/gcm/gs.jd b/docs/html/google/gcm/gs.jd
index 8ceea0cc8b0d6eadf1d441056d72857468120e2f..f6b7ebe8244b1787af065ae8590cdb11942fd771 100644
--- a/docs/html/google/gcm/gs.jd
+++ b/docs/html/google/gcm/gs.jd
@@ -1,4 +1,4 @@
-page.title=Getting Started with GCM
+page.title=Getting Started
page.tags="cloud","push","messaging"
@jd:body
@@ -12,8 +12,7 @@ page.tags="cloud","push","messaging"
Creating a Google API Project
Enabling the GCM Service
Obtaining an API Key
- Writing a Client App
- Writing the Server Code
+ Next Steps
See Also
@@ -26,12 +25,12 @@ page.tags="cloud","push","messaging"
-The sections below guide you through the process of setting up a GCM
+ This document tells you how to get started setting up a GCM
implementation.
-Before you start, make sure to set up
-the Google Play Services SDK. You need this SDK to use the {@code GoogleCloudMessaging} methods.
-
-Note that a full GCM implementation requires a server-side implementation, in addition to the client implementation in your app. This document offers a complete example that includes both the client and server.
+Before you begin, make sure to set up
+the Google Play Services SDK. You need this SDK to use the
+
+{@code GoogleCloudMessaging} methods.
Creating a Google API project
@@ -41,13 +40,17 @@ the Google Play Services SDK. You need this SDK to use the
-Note: If you already have existing projects, the first page you see will be the Dashboard page. From there you can create a new project by opening the project drop-down menu (upper left corner) and choosing Other projects > Create.
+Note: If you already have existing projects,
+the first page you see will be the Dashboard page. From there
+you can create a new project by opening the project drop-down menu (upper left corner)
+and choosing Other projects > Create.
Click Create project.
Your browser URL will change to something like:
https://code.google.com/apis/console/#project:4815162342
- Take note of the value after #project: (4815162342 in this example). This is your project number, and it will be used later on as the GCM sender ID.
+ Take note of the value after #project: (4815162342 in this
+example). This is your project number, and it will be used later on as the GCM sender ID.
Enabling the GCM Service
@@ -61,463 +64,53 @@ the Google Play Services SDK. You need this SDK to use the Obtaining an API Key
To obtain an API key:
- - In the main Google APIs Console page, select API Access. You will see a screen that resembles the following:
-
+ - In the main Google APIs Console page, select API Access.
+You will see a screen that resembles the following:
-
+
- - Click Create new Server key. Either a server key or a browser key should work. The advantage to using a server key is that it allows you to whitelist IP addresses. The following screen appears:
+ - Click Create new Server key. Either a server key or a
+browser key should work. The advantage to using a server key is that it allows
+you to whitelist IP addresses. The following screen appears:
-
+
- - Click Create:
+ - Click Create:
-
+
- Take note of the API key value (YourKeyWillBeShownHere ) in this example, as it will be used later on.
-Note: If you need to rotate the key, click Generate new key. A new key will be created while the old one will still be active for up to 24 hours. If you want to get rid of the old key immediately (for example, if you feel it was compromised), click Delete key.
-
-The following sections walk you through the steps of creating client and server-side code.
-
-Writing a Client App
-
-This section walks you through the steps involved in writing a client-side application—that is, the GCM-enabled application that runs on an Android device. This client sample is designed to work in conjunction with the server code shown in Writing the Server Code, below.
-
-
-
-Step 1: Edit Your App's Manifest
-
- - The
com.google.android.c2dm.permission.RECEIVE permission so the Android application can register and receive messages.
- - The
android.permission.INTERNET permission so the Android application can send the registration ID to the 3rd party server.
- - The
android.permission.GET_ACCOUNTS permission as GCM requires a Google account (necessary only if if the device is running a version lower than Android 4.0.4)
- - The
android.permission.WAKE_LOCK permission so the application can keep the processor from sleeping when a message is received. Optional—use only if the app wants to keep the device from sleeping.
- - An
applicationPackage + ".permission.C2D_MESSAGE" permission to prevent other Android applications from registering and receiving the Android application's
-messages. The permission name must exactly match this pattern—otherwise the Android application will not receive the messages.
- - A receiver for
com.google.android.c2dm.intent.RECEIVE , with the category set
-as applicationPackage . The receiver should require the com.google.android.c2dm.SEND permission, so that only the GCM
-Framework can send a message to it. Note that the receiving
-of messages is implemented as an intent.
- - An intent service to handle the intents received by the broadcast receiver. Optional.
- - If the GCM feature is critical to the Android application's function, be sure to
-set
android:minSdkVersion="8" in the manifest. This
-ensures that the Android application cannot be installed in an environment in which it
-could not run properly.
-
-
-Here are excerpts from a manifest that supports GCM:
-
-
-<manifest package="com.example.gcm" ...>
-
- <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.GET_ACCOUNTS" />
- <uses-permission android:name="android.permission.WAKE_LOCK" />
- <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
-
- <permission android:name="com.example.gcm.permission.C2D_MESSAGE"
- android:protectionLevel="signature" />
- <uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
-
- <application ...>
- <receiver
- android:name=".MyBroadcastReceiver"
- android:permission="com.google.android.c2dm.permission.SEND" >
- <intent-filter>
- <action android:name="com.google.android.c2dm.intent.RECEIVE" />
- <category android:name="com.example.gcm" />
- </intent-filter>
- </receiver>
- <service android:name=".MyIntentService" />
- </application>
-
-</manifest>
-
-
-
-Step 2: Register for GCM
-
-An Android application running on a mobile device registers to receive messages by calling
-the {@code GoogleCloudMessaging} method
-{@code register(senderID...)}.
-This method registers the application for GCM and returns the registration ID. This streamlined approach replaces the previous
-GCM registration process. See the example below for details.
-
- Step 3: Write Your Application
-
-Finally, write your application. GCM offers a variety of ways to get the job done:
-
-
-
-
-
-
-Example
-
-Here is a sample client application that illustrates how to use the {@code GoogleCloudMessaging} APIs. The sample consists of a main activity ({@code DemoActivity}) and a broadcast receiver ({@code GcmBroadcastReceiver}). You can use this client sample code in conjunction with the server code shown in Writing the Server Code.
-
-Note the following:
-
-
- - The sample primarily illustrates two things: registration, and upstream messaging. Upstream messaging only applies to apps that are running against a CCS server; HTTP-based servers don't support upstream messaging.
- - The {@code GoogleCloudMessaging} registration APIs replace the old registration process, which was based on the now-obsolete client helper library. While the old registration process still works, we encourage you to use the newer {@code GoogleCloudMessaging} registration APIs, regardless of your underlying server.
-
-
-Registering
-An Android application needs to register with GCM servers before it can receive messages. So in its {@code onCreate()} method, {@code DemoActivity} checks to see whether the app is registered with GCM and with the server:
-
-/**
- * Main UI for the demo app.
- */
-public class DemoActivity extends Activity {
-
- public static final String EXTRA_MESSAGE = "message";
- public static final String PROPERTY_REG_ID = "registration_id";
- private static final String PROPERTY_APP_VERSION = "appVersion";
- private static final String PROPERTY_ON_SERVER_EXPIRATION_TIME =
- "onServerExpirationTimeMs";
- /**
- * Default lifespan (7 days) of a reservation until it is considered expired.
- */
- public static final long REGISTRATION_EXPIRY_TIME_MS = 1000 * 3600 * 24 * 7;
-
- /**
- * Substitute you own sender ID here.
- */
- String SENDER_ID = "Your-Sender-ID";
-
- /**
- * Tag used on log messages.
- */
- static final String TAG = "GCMDemo";
-
- TextView mDisplay;
- GoogleCloudMessaging gcm;
- AtomicInteger msgId = new AtomicInteger();
- SharedPreferences prefs;
- Context context;
-
- String regid;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.main);
- mDisplay = (TextView) findViewById(R.id.display);
-
- context = getApplicationContext();
- regid = getRegistrationId(context);
-
- if (regid.length() == 0) {
- registerBackground();
- }
- gcm = GoogleCloudMessaging.getInstance(this);
- }
-...
-}
-
-The app calls {@code getRegistrationId()} to see whether there is an existing registration ID stored in shared preferences:
-
-/**
- * Gets the current registration id for application on GCM service.
- * <p>
- * If result is empty, the registration has failed.
- *
- * @return registration id, or empty string if the registration is not
- * complete.
- */
-private String getRegistrationId(Context context) {
- final SharedPreferences prefs = getGCMPreferences(context);
- String registrationId = prefs.getString(PROPERTY_REG_ID, "");
- if (registrationId.length() == 0) {
- Log.v(TAG, "Registration not found.");
- return "";
- }
- // check if app was updated; if so, it must clear registration id to
- // avoid a race condition if GCM sends a message
- int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
- int currentVersion = getAppVersion(context);
- if (registeredVersion != currentVersion || isRegistrationExpired()) {
- Log.v(TAG, "App version changed or registration expired.");
- return "";
- }
- return registrationId;
-}
+ Take note of the API key value (YourKeyWillBeShownHere )
+in this example, as it will be used later on.
+Note: If you need to rotate the key, click
+Generate new key. A new key will be created while the old one
+will still be active for up to 24 hours. If you want to get rid of the old key
+immediately (for example, if you feel it was compromised), click Delete key.
-...
+Next Steps
-/**
- * @return Application's {@code SharedPreferences}.
- */
-private SharedPreferences getGCMPreferences(Context context) {
- return getSharedPreferences(DemoActivity.class.getSimpleName(),
- Context.MODE_PRIVATE);
-}
-
-If the registration ID doesn't exist, or the app was updated, or the registration ID has expired, {@code getRegistrationId()} returns an empty string to indicate that the app needs to get a new regID. {@code getRegistrationId()} calls the following methods to check the app version and whether the regID has expired:
-
-/**
- * @return Application's version code from the {@code PackageManager}.
- */
-private static int getAppVersion(Context context) {
- try {
- PackageInfo packageInfo = context.getPackageManager()
- .getPackageInfo(context.getPackageName(), 0);
- return packageInfo.versionCode;
- } catch (NameNotFoundException e) {
- // should never happen
- throw new RuntimeException("Could not get package name: " + e);
- }
-}
-
-/**
- * Checks if the registration has expired.
- *
- * <p>To avoid the scenario where the device sends the registration to the
- * server but the server loses it, the app developer may choose to re-register
- * after REGISTRATION_EXPIRY_TIME_MS.
- *
- * @return true if the registration has expired.
- */
-private boolean isRegistrationExpired() {
- final SharedPreferences prefs = getGCMPreferences(context);
- // checks if the information is not stale
- long expirationTime =
- prefs.getLong(PROPERTY_ON_SERVER_EXPIRATION_TIME, -1);
- return System.currentTimeMillis() > expirationTime;
-}
-
-
-If there isn't a valid existing registration ID, {@code DemoActivity} calls the following {@code registerBackground()} method to register. Note that because GCM methods are blocking, this has to take place on a background thread. This sample uses {@link android.os.AsyncTask} to accomplish this:
-
-
-/**
- * Registers the application with GCM servers asynchronously.
- * <p>
- * Stores the registration id, app versionCode, and expiration time in the
- * application's shared preferences.
- */
-private void registerBackground() {
- new AsyncTask() {
- @Override
- protected String doInBackground(Void... params) {
- String msg = "";
- try {
- if (gcm == null) {
- gcm = GoogleCloudMessaging.getInstance(context);
- }
- regid = gcm.register(SENDER_ID);
- msg = "Device registered, registration id=" + regid;
-
- // You should send the registration ID to your server over HTTP,
- // so it can use GCM/HTTP or CCS to send messages to your app.
-
- // For this demo: we don't need to send it because the device
- // will send upstream messages to a server that echo back the message
- // using the 'from' address in the message.
-
- // Save the regid - no need to register again.
- setRegistrationId(context, regid);
- } catch (IOException ex) {
- msg = "Error :" + ex.getMessage();
- }
- return msg;
- }
-
- @Override
- protected void onPostExecute(String msg) {
- mDisplay.append(msg + "\n");
- }
- }.execute(null, null, null);
-}
-
-After registering, the app calls {@code setRegistrationId()} to store the registration ID in shared preferences for future use:
-
-/**
- * Stores the registration id, app versionCode, and expiration time in the
- * application's {@code SharedPreferences}.
- *
- * @param context application's context.
- * @param regId registration id
- */
-private void setRegistrationId(Context context, String regId) {
- final SharedPreferences prefs = getGCMPreferences(context);
- int appVersion = getAppVersion(context);
- Log.v(TAG, "Saving regId on app version " + appVersion);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putString(PROPERTY_REG_ID, regId);
- editor.putInt(PROPERTY_APP_VERSION, appVersion);
- long expirationTime = System.currentTimeMillis() + REGISTRATION_EXPIRY_TIME_MS;
-
- Log.v(TAG, "Setting registration expiry time to " +
- new Timestamp(expirationTime));
- editor.putLong(PROPERTY_ON_SERVER_EXPIRATION_TIME, expirationTime);
- editor.commit();
-}
-
-Sending a message
-When the user clicks the app's Send button, the app sends an upstream message using the new {@code GoogleCloudMessaging} APIs. In order to receive the upstream message, your server should be connected to CCS. You can use the code shown in Writing the Server Code as a sample XMPP client to connect to CCS.
-
-public void onClick(final View view) {
- if (view == findViewById(R.id.send)) {
- new AsyncTask() {
- @Override
- protected String doInBackground(Void... params) {
- String msg = "";
- try {
- Bundle data = new Bundle();
- data.putString("hello", "World");
- String id = Integer.toString(msgId.incrementAndGet());
- gcm.send(SENDER_ID + "@gcm.googleapis.com", id, data);
- msg = "Sent message";
- } catch (IOException ex) {
- msg = "Error :" + ex.getMessage();
- }
- return msg;
- }
-
- @Override
- protected void onPostExecute(String msg) {
- mDisplay.append(msg + "\n");
- }
- }.execute(null, null, null);
- } else if (view == findViewById(R.id.clear)) {
- mDisplay.setText("");
- }
-}
-
-As described above in Step 1, the app includes a broadcast receiver for the com.google.android.c2dm.intent.RECEIVE intent. This is the mechanism GCM uses to deliver messages. When {@code onClick()} calls {@code gcm.send()}, it triggers the broadcast receiver's {@code onReceive()} method, which has the responsibility of handling the GCM message. In this sample the receiver's {@code onReceive()} method calls {@code sendNotification()} to put the message into a notification:
-
-/**
- * Handling of GCM messages.
- */
-public class GcmBroadcastReceiver extends BroadcastReceiver {
- static final String TAG = "GCMDemo";
- public static final int NOTIFICATION_ID = 1;
- private NotificationManager mNotificationManager;
- NotificationCompat.Builder builder;
- Context ctx;
- @Override
- public void onReceive(Context context, Intent intent) {
- GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
- ctx = context;
- String messageType = gcm.getMessageType(intent);
- if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
- sendNotification("Send error: " + intent.getExtras().toString());
- } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
- sendNotification("Deleted messages on server: " +
- intent.getExtras().toString());
- } else {
- sendNotification("Received: " + intent.getExtras().toString());
- }
- setResultCode(Activity.RESULT_OK);
- }
-
- // Put the GCM message into a notification and post it.
- private void sendNotification(String msg) {
- mNotificationManager = (NotificationManager)
- ctx.getSystemService(Context.NOTIFICATION_SERVICE);
-
- PendingIntent contentIntent = PendingIntent.getActivity(ctx, 0,
- new Intent(ctx, DemoActivity.class), 0);
-
- NotificationCompat.Builder mBuilder =
- new NotificationCompat.Builder(ctx)
- .setSmallIcon(R.drawable.ic_stat_gcm)
- .setContentTitle("GCM Notification")
- .setStyle(new NotificationCompat.BigTextStyle()
- .bigText(msg))
- .setContentText(msg);
-
- mBuilder.setContentIntent(contentIntent);
- mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
- }
-}
-
-Writing the Server Code
-
-Here is an example of a CCS server written in Python. You can use this in conjunction with the sample client code shown above. This sample echo server sends an initial message, and for every upstream message received, it sends a dummy response back to the application that sent the upstream message. This example illustrates how to connect,
-send, and receive GCM messages using XMPP. It shouldn't be used as-is
-on a production deployment. For examples of HTTP-based servers, see GCM Server.
-
-
-#!/usr/bin/python
-import sys, json, xmpp, random, string
-
-SERVER = 'gcm.googleapis.com'
-PORT = 5235
-USERNAME = ''
-PASSWORD = ''
-REGISTRATION_ID = ''
-
-unacked_messages_quota = 1000
-send_queue = []
-
-# Return a random alphanumerical id
-def random_id():
- rid = ''
- for x in range(8): rid += random.choice(string.ascii_letters + string.digits)
- return rid
-
-def message_callback(session, message):
- global unacked_messages_quota
- gcm = message.getTags('gcm')
- if gcm:
- gcm_json = gcm[0].getData()
- msg = json.loads(gcm_json)
- if not msg.has_key('message_type'):
- # Acknowledge the incoming message immediately.
- send({'to': msg['from'],
- 'message_type': 'ack',
- 'message_id': msg['message_id']})
- # Queue a response back to the server.
- if msg.has_key('from'):
- # Send a dummy echo response back to the app that sent the upstream message.
- send_queue.append({'to': msg['from'],
- 'message_id': random_id(),
- 'data': {'pong': 1}})
- elif msg['message_type'] == 'ack' or msg['message_type'] == 'nack':
- unacked_messages_quota += 1
-
-def send(json_dict):
- template = ("<message><gcm xmlns='google:mobile:data'>{1}</gcm></message>")
- client.send(xmpp.protocol.Message(
- node=template.format(client.Bind.bound[0], json.dumps(json_dict))))
-
-def flush_queued_messages():
- global unacked_messages_quota
- while len(send_queue) and unacked_messages_quota > 0:
- send(send_queue.pop(0))
- unacked_messages_quota -= 1
-
-client = xmpp.Client('gcm.googleapis.com', debug=['socket'])
-client.connect(server=(SERVER,PORT), secure=1, use_srv=False)
-auth = client.auth(USERNAME, PASSWORD)
-if not auth:
- print 'Authentication failed!'
- sys.exit(1)
-
-client.RegisterHandler('message', message_callback)
-
-send_queue.append({'to': REGISTRATION_ID,
- 'message_id': 'reg_id',
- 'data': {'message_destination': 'RegId',
- 'message_id': random_id()}})
-
-while True:
- client.Process(1)
- flush_queued_messages()
+Once you've finished the tasks listed above, you're ready to start
+implementing GCM. Here is an overview of the basic steps:
+
+ - Decide which Google-provided GCM connection server you want to use—
+ HTTP or XMPP (CCS). GCM connection servers
+take messages from a 3rd-party application
+server (written by you) and send them to a GCM-enabled Android application (the
+"client app," also written by you) running on a device.
+ - Implement an application server (the "3rd-party application server") to interact
+with your chosen GCM connection server. The app server sends data to a
+GCM-enabled Android client application via the GCM connection server. For more
+information about implementing the server side, see
+Implementing GCM Server.
+- Write your client app. This is the GCM-enabled Android application that runs
+on a device. See Implementing GCM Client for more information.
+
diff --git a/docs/html/google/gcm/http.jd b/docs/html/google/gcm/http.jd
new file mode 100644
index 0000000000000000000000000000000000000000..b8d8659d941eda13f2430a0f7a22b9f8ef03cdb8
--- /dev/null
+++ b/docs/html/google/gcm/http.jd
@@ -0,0 +1,618 @@
+page.title=GCM HTTP Connection Server
+@jd:body
+
+
+
+This document describes the GCM HTTP connection server. Connection servers
+are the Google-provided servers that take messages from the 3rd-party
+application server and sending them to the device.
+
+
+
+Note: See
+Implementing GCM Server for a list of all the message
+parameters and which connection server(s) supports them.
+
+
+Authentication
+
+To send a message, the application server issues a POST request to
+https://android.googleapis.com/gcm/send .
+A message request is made of 2 parts: HTTP header and HTTP body.
+
+The HTTP header must contain the following headers:
+
+ Authorization : key=YOUR_API_KEY
+ Content-Type : application/json for JSON; application/x-www-form-urlencoded;charset=UTF-8 for plain text.
+
+
+
+For example:
+
+Content-Type:application/json
+Authorization:key=AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA
+
+{
+ "registration_ids" : ["APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx..."],
+ "data" : {
+ ...
+ },
+}
+
+ Note: If Content-Type is omitted, the format
+is assumed to be plain text.
+
+
+The HTTP body content depends on whether you're using JSON or plain text.
+See
+Implementing GCM Server for a list of all the
+parameters your JSON or plain text message can contain.
+
+
+ Request Format
+ Here is the smallest possible request (a message without any parameters and
+just one recipient) using JSON:
+ { "registration_ids": [ "42" ] }
+
+ And here the same example using plain text:
+ registration_id=42
+
+ Here is a message with a payload and 6 recipients:
+ { "data": {
+ "score": "5x1",
+ "time": "15:10"
+ },
+ "registration_ids": ["4", "8", "15", "16", "23", "42"]
+}
+ Here is a message with all optional fields set and 6 recipients:
+ { "collapse_key": "score_update",
+ "time_to_live": 108,
+ "delay_while_idle": true,
+ "data": {
+ "score": "4x8",
+ "time": "15:16.2342"
+ },
+ "registration_ids":["4", "8", "15", "16", "23", "42"]
+}
+ And here is the same message using plain-text format (but just 1 recipient):
+
+ collapse_key=score_update&time_to_live=108&delay_while_idle=1&data.score=4x8&data.time=15:16.2342®istration_id=42
+
+
+Note: If your organization has a firewall
+that restricts the traffic to or
+from the Internet, you need to configure it to allow connectivity with GCM in order for
+your Android devices to receive messages.
+The ports to open are: 5228, 5229, and 5230. GCM typically only uses 5228, but
+it sometimes uses 5229 and 5230. GCM doesn't provide specific IPs, so you should allow
+your firewall to accept outgoing connections to all IP addresses
+contained in the IP blocks listed in Google's ASN of 15169.
+
+
+
+Response format
+
+There are two possible outcomes when trying to send a message:
+
+ - The message is processed successfully.
+ - The GCM server rejects the request.
+
+
+When the message is processed successfully, the HTTP response has a 200 status
+and the body contains more information about the status of the message
+(including possible errors). When the request is rejected,
+the HTTP response contains a non-200 status code (such as 400, 401, or 503).
+
+The following table summarizes the statuses that the HTTP response header might
+contain. Click the troubleshoot link for advice on how to deal with each type of
+error.
+
+
+ Response |
+ Description |
+
+
+ 200 |
+ Message was processed successfully. The response body will contain more
+details about the message status, but its format will depend whether the request
+was JSON or plain text. See Interpreting a success response
+for more details. |
+
+
+ 400 |
+ Only applies for JSON requests.
+Indicates that the request could not be parsed as JSON, or it contained invalid
+fields (for instance, passing a string where a number was expected). The exact
+failure reason is described in the response and the problem should be addressed
+before the request can be retried. |
+
+
+ 401 |
+ There was an error authenticating the sender account.
+Troubleshoot |
+
+
+ 5xx |
+ Errors in the 500-599 range (such as 500 or 503) indicate that there wa
+an internal error in the GCM server while trying to process the request, or that
+the server is temporarily unavailable (for example, because of timeouts). Sender
+must retry later, honoring any Retry-After header included in the
+response. Application servers must implement exponential back-off.
+Troubleshoot |
+
+
+
+Interpreting a success response
+When a JSON request is successful (HTTP status code 200), the response body
+contains a JSON object with the following fields:
+
+
+ Field |
+ Description |
+
+
+ multicast_id |
+ Unique ID (number) identifying the multicast message. |
+
+
+ success |
+ Number of messages that were processed without an error. |
+
+
+ failure |
+ Number of messages that could not be processed. |
+
+
+ canonical_ids |
+ Number of results that contain a canonical registration ID. See
+Advanced Topics for more discussion of this topic. |
+
+
+ results |
+ Array of objects representing the status of the messages processed. The
+objects are listed in the same order as the request (i.e., for each registration
+ID in the request, its result is listed in the same index in the response) and
+they can have these fields:
+
+ message_id : String representing the message when it was
+successfully processed.
+ registration_id : If set, means that GCM processed the
+message but it has another canonical registration ID for that device, so sender
+should replace the IDs on future requests (otherwise they might be rejected).
+This field is never set if there is an error in the request.
+
+ error : String describing an error that occurred while
+processing the message for that recipient. The possible values are the same as
+documented in the above table, plus "Unavailable" (meaning GCM servers
+were busy and could not process the message for that particular recipient, so
+it could be retried).
+ |
+
+
+If the value of failure and canonical_ids is 0, it's
+not necessary to parse the remainder of the response. Otherwise, we recommend
+that you iterate through the results field and do the following for each object
+in that list:
+
+ - If
message_id is set, check for registration_id :
+
+ - If
registration_id is set, replace the original ID with
+the new value (canonical ID) in your server database. Note that the original ID
+is not part of the result, so you need to obtain it from the list of
+code>registration_ids passed in the request (using the same index).
+
+
+ - Otherwise, get the value of
error :
+
+ - If it is
Unavailable , you could retry to send it in another
+request.
+ - If it is
NotRegistered , you should remove the registration
+ID from your server database because the application was uninstalled from the
+device or it does not have a broadcast receiver configured to receive
+com.google.android.c2dm.intent.RECEIVE intents.
+ - Otherwise, there is something wrong in the registration ID passed in
+the request; it is probably a non-recoverable error that will also require removing
+the registration from the server database. See Interpreting
+an error response for all possible error values.
+
+
+
+
+When a plain-text request is successful (HTTP status code 200), the response
+body contains 1 or 2 lines in the form of key/value pairs.
+The first line is always available and its content is either id=ID of
+sent message or Error=GCM error code . The second
+line, if available,
+has the format of registration_id=canonical ID . The second
+line is optional, and it can only be sent if the first line is not an error. We
+recommend handling the plain-text response in a similar way as handling the
+JSON response:
+
+ - If first line starts with
id , check second line:
+
+ - If second line starts with
registration_id , gets its value
+and replace the registration IDs in your server database.
+
+
+ - Otherwise, get the value of
Error :
+
+ - If it is
NotRegistered , remove the registration ID from
+your server database.
+ - Otherwise, there is probably a non-recoverable error (Note:
+Plain-text requests will never return
Unavailable as the
+error code, they would have returned a 500 HTTP status instead).
+
+
+
+
+Interpreting an error response
+Here are the recommendations for handling the different types of error that
+might occur when trying to send a message to a device:
+
+
+- Missing Registration ID
+- Check that the request contains a registration ID (either in the
+
registration_id parameter in a plain text message, or in the
+registration_ids field in JSON).
+ Happens when error code is MissingRegistration .
+
+- Invalid Registration ID
+- Check the formatting of the registration ID that you pass to the server. Make
+sure it matches the registration ID the phone receives in the
+
com.google.android.c2dm.intent.REGISTRATION intent and that you're
+not truncating it or adding additional characters.
+ Happens when error code is InvalidRegistration .
+
+- Mismatched Sender
+- A registration ID is tied to a certain group of senders. When an application
+registers for GCM usage, it must specify which senders are allowed to send messages.
+Make sure you're using one of those when trying to send messages to the device.
+If you switch to a different sender, the existing registration IDs won't work.
+Happens when error code is
MismatchSenderId .
+
+- Unregistered Device
+- An existing registration ID may cease to be valid in a number of scenarios, including:
+
+ - If the application manually unregisters by issuing a
+
+
com.google.android.c2dm.intent.UNREGISTER
+ intent.
+ - If the application is automatically unregistered, which can happen
+(but is not guaranteed) if the user uninstalls the application.
+ - If the registration ID expires. Google might decide to refresh registration
+IDs.
+ - If the application is updated but the new version does not have a broadcast
+receiver configured to receive
com.google.android.c2dm.intent.RECEIVE
+intents.
+
+For all these cases, you should remove this registration ID from the 3rd-party
+server and stop using it to send
+messages.
+ Happens when error code is NotRegistered .
+
+- Message Too Big
+ - The total size of the payload data that is included in a message can't
+exceed 4096 bytes. Note that this includes both the size of the keys as well
+as the values.
+
Happens when error code is MessageTooBig .
+
+- Invalid Data Key
+- The payload data contains a key (such as
from or any value
+prefixed by google. ) that is used internally by GCM in the
+com.google.android.c2dm.intent.RECEIVE Intent and cannot be used.
+Note that some words (such as collapse_key ) are also used by GCM
+but are allowed in the payload, in which case the payload value will be
+overridden by the GCM value.
+
+Happens when the error code is InvalidDataKey .
+
+- Invalid Time To Live
+ - The value for the Time to Live field must be an integer representing
+a duration in seconds between 0 and 2,419,200 (4 weeks). Happens when error code
+is
InvalidTtl .
+
+
+ - Authentication Error
+ - The sender account that you're trying to use to send a message couldn't be
+authenticated. Possible causes are:
+- Authorization header missing or with invalid syntax.
+- Invalid project number sent as key.
+- Key valid but with GCM service disabled.
+- Request originated from a server not whitelisted in the Server Key IPs.
+
+
+Check that the token you're sending inside the Authorization header
+is the correct API key associated with your project. You can check the validity
+of your API key by running the following command:
+
+# api_key=YOUR_API_KEY
+
+# curl --header "Authorization: key=$api_key" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"ABC\"]}"
+
+
+
+If you receive a 401 HTTP status code, your API key is not valid. Otherwise you
+should see something like this:
+
+
+{"multicast_id":6782339717028231855,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
+
+If you want to confirm the validity of a registration ID, you can do so by
+replacing "ABC" with the registration ID.
+
+Happens when the HTTP status code is 401.
+
+ - Timeout
+
+- The server couldn't process the request in time. You should retry the
+same request, but you MUST obey the following requirements:
+
+
+
+- Honor the
Retry-After header if it's included in the response
+from the GCM server.
+
+
+- Implement exponential back-off in your retry mechanism. This means an
+exponentially increasing delay after each failed retry (e.g. if you waited one
+second before the first retry, wait at least two second before the next one,
+then 4 seconds and so on). If you're sending multiple messages, delay each one
+independently by an additional random amount to avoid issuing a new request for
+all messages at the same time.
+
+
+Senders that cause problems risk being blacklisted.
+
+Happens when the HTTP status code is between 501 and 599, or when the
+error field of a JSON object in the results array is Unavailable .
+
+
+- Internal Server Error
+
+-
+The server encountered an error while trying to process the request. You
+could retry the same request (obeying the requirements listed in the Timeout
+section), but if the error persists, please report the problem in the
+android-gcm group.
+
+Happens when the HTTP status code is 500, or when the error field of a JSON
+object in the results array is InternalServerError .
+
+
+- Invalid Package Name
+
+-
+A message was addressed to a registration ID whose package name did not match
+the value passed in the request. Happens when error code is
+
InvalidPackageName .
+
+
+
+Example responses
+This section shows a few examples of responses indicating messages that were
+processed successfully. See Request Format for
+the requests these responses are based on.
+ Here is a simple case of a JSON message successfully sent to one recipient
+without canonical IDs in the response:
+{ "multicast_id": 108,
+ "success": 1,
+ "failure": 0,
+ "canonical_ids": 0,
+ "results": [
+ { "message_id": "1:08" }
+ ]
+}
+
+Or if the request was in plain-text format:
+id=1:08
+
+
+Here are JSON results for 6 recipients (IDs 4, 8, 15, 16, 23, and 42 respectively)
+with 3 messages successfully processed, 1 canonical registration ID returned,
+and 3 errors:
+{ "multicast_id": 216,
+ "success": 3,
+ "failure": 3,
+ "canonical_ids": 1,
+ "results": [
+ { "message_id": "1:0408" },
+ { "error": "Unavailable" },
+ { "error": "InvalidRegistration" },
+ { "message_id": "1:1516" },
+ { "message_id": "1:2342", "registration_id": "32" },
+ { "error": "NotRegistered"}
+ ]
+}
+
+ In this example:
+
+ - First message: success, not required.
+ - Second message: should be resent (to registration ID 8).
+ - Third message: had an unrecoverable error (maybe the value got corrupted
+in the database).
+ - Fourth message: success, nothing required.
+ - Fifth message: success, but the registration ID should be updated in the
+server database (from 23 to 32).
+ - Sixth message: registration ID (42) should be removed from the server database
+because the application was uninstalled from the device.
+
+Or if just the 4th message above was sent using plain-text format:
+Error=InvalidRegistration
+
+If the 5th message above was also sent using plain-text format:
+id=1:2342
+registration_id=32
+
+
+
+Implementing an HTTP-Based App Server
+
+This section gives examples of implementing an app server that works with the
+GCM HTTP connection server. Note that a full GCM implementation requires a
+client-side implementation, in addition to the server.
+
+
+ Requirements
+For the web server:
+
+ - Ant 1.8 (it might work with earlier versions, but it's not guaranteed).
+ - One of the following:
+
+
+ - A Google account registered to use GCM.
+ - The API key for that account.
+
+For the Android application:
+
+ - Emulator (or device) running Android 2.2 with Google APIs.
+ - The Google API project number of the account registered to use GCM.
+
+
+Setting Up GCM
+Before proceeding with the server and client setup, it's necessary to register
+a Google account with the Google API Console, enable Google Cloud Messaging in GCM,
+and obtain an API key from the
+Google API Console.
+For instructions on how to set up GCM, see Getting Started.
+
+
+Setting Up an HTTP Server
+This section describes the different options for setting up an HTTP server.
+
+Using a standard web server
+To set up the server using a standard, servlet-compliant web server:
+
+ - From the open source site,
+download the following directories:
gcm-server ,
+samples/gcm-demo-server , and samples/gcm-demo-appengine .
+
+
+ - In a text editor, edit the
samples/gcm-demo-server/WebContent/WEB-INF/classes/api.key and replace the existing text with the API key obtained above.
+ - In a shell window, go to the
samples/gcm-demo-server directory.
+ - Generate the server's WAR file by running
ant war :
+
+ $ ant war
+
+Buildfile:build.xml
+
+init:
+ [mkdir] Created dir: build/classes
+ [mkdir] Created dir: dist
+
+compile:
+ [javac] Compiling 6 source files to build/classes
+
+war:
+ [war] Building war: dist/gcm-demo.war
+
+BUILD SUCCESSFUL
+Total time: 0 seconds
+
+
+ - Deploy the
dist/gcm-demo.war to your running server. For instance, if you're using Jetty, copy gcm-demo.war to the webapps directory of the Jetty installation.
+ - Open the server's main page in a browser. The URL depends on the server you're using and your machine's IP address, but it will be something like
http://192.168.1.10:8080/gcm-demo/home , where gcm-demo is the application context and /home is the path of the main servlet.
+
+
+
+Note: You can get the IP by running ifconfig on Linux or MacOS, or ipconfig on Windows.
+
+ You server is now ready.
+
+Using App Engine for Java
+
+To set up the server using a standard App Engine for Java:
+
+ - Get the files from the open source
+site, as described above.
+
+ - In a text editor, edit
+
samples/gcm-demo-appengine/src/com/google/android/gcm/demo/server/ApiKeyInitializer.java
+and replace the existing text with the API key obtained above.
+
+ Note: The API key value set in that class will
+be used just once to create a persistent entity on App Engine. If you deploy
+the application, you can use App Engine's Datastore Viewer to change
+it later.
+
+
+ - In a shell window, go to the
samples/gcm-demo-appengine directory.
+ - Start the development App Engine server by
ant runserver ,
+using the -Dsdk.dir to indicate the location of the App Engine SDK
+and -Dserver.host to set your server's hostname or IP address:
+
+
+$ ant -Dsdk.dir=/opt/google/appengine-java-sdk runserver -Dserver.host=192.168.1.10
+Buildfile: gcm-demo-appengine/build.xml
+
+init:
+ [mkdir] Created dir: gcm-demo-appengine/dist
+
+copyjars:
+
+compile:
+
+datanucleusenhance:
+ [enhance] DataNucleus Enhancer (version 1.1.4) : Enhancement of classes
+ [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=28 ms, enhance=0 ms, total=28 ms. Consult the log for full details
+ [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details
+
+runserver:
+ [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.jetty.JettyLogger info
+ [java] INFO: Logging to JettyLogger(null) via com.google.apphosting.utils.jetty.JettyLogger
+ [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.config.AppEngineWebXmlReader readAppEngineWebXml
+ [java] INFO: Successfully processed gcm-demo-appengine/WebContent/WEB-INF/appengine-web.xml
+ [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.config.AbstractConfigXmlReader readConfigXml
+ [java] INFO: Successfully processed gcm-demo-appengine/WebContent/WEB-INF/web.xml
+ [java] Jun 15, 2012 8:46:09 PM com.google.android.gcm.demo.server.ApiKeyInitializer contextInitialized
+ [java] SEVERE: Created fake key. Please go to App Engine admin console, change its value to your API Key (the entity type is 'Settings' and its field to be changed is 'ApiKey'), then restart the server!
+ [java] Jun 15, 2012 8:46:09 PM com.google.appengine.tools.development.DevAppServerImpl start
+ [java] INFO: The server is running at http://192.168.1.10:8080/
+ [java] Jun 15, 2012 8:46:09 PM com.google.appengine.tools.development.DevAppServerImpl start
+ [java] INFO: The admin console is running at http://192.168.1.10:8080/_ah/admin
+
+
+ - Open the server's main page in a browser. The URL depends on the server
+you're using and your machine's IP address, but it will be something like
+
http://192.168.1.10:8080/home , where /home
+is the path of the main servlet.
+
+ Note: You can get the IP by running ifconfig
+on Linux or MacOS, or ipconfig on Windows.
+
+
+ You server is now ready.
diff --git a/docs/html/google/gcm/notifications.jd b/docs/html/google/gcm/notifications.jd
index 5171850422488fc702ee400df5a14b8da2fcc26e..43a7368f7eae0b6ac621bbd3904e5be126e4daa1 100644
--- a/docs/html/google/gcm/notifications.jd
+++ b/docs/html/google/gcm/notifications.jd
@@ -14,14 +14,15 @@ page.title=User Notifications
In this document
- - What are User Notifications?
- - Examples
-
- - Generate a notification key
- - Add registration IDs
- - Remove registration IDs
- - Send upstream messages
- - Response formats
+ - Request Format
+ - Generate a Notification Key
+ - Add Registration IDs
+ - Remove Registration IDs
+ - Send Upstream Messages
+ - Response Formats
+
+ - Create/add/remove operations
+
- Send operations
@@ -38,32 +39,51 @@ page.title=User Notifications
Note: To try out this feature, sign up using this form.
-The upstream messaging (device-to-cloud) feature described in this document is part of the Google Play services platform. Upstream messaging is available through the GoogleCloudMessaging APIs. To use upstream messaging and the new streamlined registration process, you must set up the Google Play services SDK.
-What are User Notifications?
-
-Third party servers can send a single message to multiple instance of an app running on devices owned by a single user. This feature is called user notifications. User notifications make it possible for every app instance that a user owns to reflect the latest messaging state. For example:
+With user notifications, 3rd-party app servers can send a single message to
+multiple instance of an app running on devices owned by a single user. This feature
+is called user notifications. User notifications make it possible for every
+app instance that a user owns to reflect the latest messaging state. For example:
- - If a message has been handled on one device, the GCM message on the other devices are dismissed. For example, if a user has handled a calendar notification on one device, the notification will go away on the user's other devices.
- - If a message has not been delivered yet to a device and but it has been handled, the GCM server removes it from the unsent queue for the other devices.
- - Likewise, a device can send messages to the {@code notification_key}, which is the token that GCM uses to fan out notifications to all devices whose registration IDs are associated with the key.
-
+ - If a message has been handled on one device, the GCM message on the other
+devices are dismissed. For example, if a user has handled a calendar notification
+on one device, the notification will go away on the user's other devices.
-The way this works is that during registration, the 3rd-party server requests a {@code notification_key}. The {@code notification_key} maps a particular user to all of the user's associated registration IDs (a regID represents a particular Android application running on a particular device). Then instead of sending one message to one regID at a time, the 3rd-party server can send a message to to the {@code notification_key}, which then sends the message to all of the user's regIDs.
+ - If a message has not been delivered yet to a device and but it has been handled,
+the GCM server removes it from the unsent queue for the other devices.
-Note: A notification dismissal message is like any other upstream message, meaning that it will be delivered to the other devices that belong to the specified {@code notification_key}. You should design your app to handle cases where the app receives a dismissal message, but has not yet displayed the notification that is being dismissed. You can solve this by caching the dismissal and then reconciling it with the corresponding notification.
-
+ - Likewise, a device can send messages to the {@code notification_key}, which
+is the token that GCM uses to fan out notifications to all devices whose
+registration IDs are associated with the key.
+
-You can use this feature with either the new GCM Cloud Connection Server (CCS), or the older GCM HTTP server.
+The way this works is that during registration, the 3rd-party server requests
+a {@code notification_key}. The {@code notification_key} maps a particular user
+to all of the user's associated registration IDs (a regID represents a particular
+Android application running on a particular device). Then instead of sending one
+message to one regID at a time, the 3rd-party server can send a message to to the
+{@code notification_key}, which then sends the message to all of the user's regIDs.
+
+Note: A notification dismissal message is like any
+other upstream message, meaning that it will be delivered to the other devices that
+belong to the specified {@code notification_key}. You should design your app to
+handle cases where the app receives a dismissal message, but has not yet displayed
+the notification that is being dismissed. You can solve this by caching the dismissal
+and then reconciling it with the corresponding notification.
+
+You can use this feature with either the XMPP (CCS) or
+HTTP connection server.
-Examples
-The examples in this section show you how to perform generate/add/remove operations, and how to send upstream messages. For generate/add/remove operations, the message body is JSON.
+The examples below show you how to perform generate/add/remove operations,
+and how to send upstream messages. For generate/add/remove operations, the
+message body is JSON.
-Request format
-To send a message, the application server issues a POST request to https://android.googleapis.com/gcm/notification .
+Request Format
+To send a message, the application server issues a POST request to
+https://android.googleapis.com/gcm/notification .
Here is the HTTP request header you should use for all create/add/remove operations:
@@ -72,12 +92,22 @@ Header : "project_id": <projectID>
Header: "Authorization", "key=API_KEY"
-Generate a notification key
+Generate a Notification Key
-This example shows how to create a new notification_key for a notification_key_name called appUser-Chris . The {@code notification_key_name} is a name or identifier (can be a username for a 3rd-party app) that is unique to a given user. It is used by third parties to group together registration IDs for a single user. Note that notification_key_name and notification_key are unique to a group of registration IDs. It is also important that notification_key_name be uniquely named per app in case you have multiple apps for the same project ID. This ensures that notifications only go to the intended target app.
+This example shows how to create a new notification_key for a
+notification_key_name called appUser-Chris .
+The {@code notification_key_name} is a name or identifier (can be a username for
+a 3rd-party app) that is unique to a given user. It is used by third parties to
+group together registration IDs for a single user. Note that notification_key_name
+and notification_key are unique to a group of registration IDs. It is also
+important that notification_key_name be uniquely named per app in case
+you have multiple apps for the same project ID. This ensures that notifications
+only go to the intended target app.
-A create operation returns a token (notification_key ). Third parties must save this token (as well as its mapping to the notification_key_name ) to use in subsequent operations:
+A create operation returns a token (notification_key ). Third parties
+must save this token (as well as its mapping to the notification_key_name )
+to use in subsequent operations:
request:
{
@@ -86,11 +116,14 @@ Header: "Authorization", "key=API_KEY"
"registration_ids": ["4", "8", "15", "16", "23", "42"]
}
-Add registration IDs
+Add Registration IDs
-This example shows how to add registration IDs for a given notification key. The maximum number of members allowed for a {@code notification_key} is 10.
+This example shows how to add registration IDs for a given notification key.
+The maximum number of members allowed for a {@code notification_key} is 10.
-Note that the notification_key_name is not strictly required for adding/removing regIDs. But including it protects you against accidentally using the incorrect notification_key .
+Note that the notification_key_name is not strictly required for
+adding/removing regIDs. But including it protects you against accidentally using
+the incorrect notification_key .
request:
{
@@ -100,7 +133,7 @@ Header: "Authorization", "key=API_KEY"
"registration_ids": ["4", "8", "15", "16", "23", "42"]
}
-Remove registration IDs
+Remove Registration IDs
This example shows how to remove registration IDs for a given notification key:
request:
@@ -111,9 +144,14 @@ Header: "Authorization", "key=API_KEY"
"registration_ids": ["4", "8", "15", "16", "23", "42"]
}
-Send upstream messages
+Send Upstream Messages
-To send an upstream (device-to-cloud) message, you must use the GoogleCloudMessaging API. Specifying a {@code notification_key} as the target for an upstream message allows a user on one device to send a message to other devices in the notification group—for example, to dismiss a notification. Here is an example that shows targeting a {@code notification_key}:
+To send an upstream (device-to-cloud) message, you must use the
+
+{@code GoogleCloudMessaging} API. Specifying a {@code notification_key} as the target
+for an upstream message allows a user on one device to send a message to other
+devices in the notification group—for example, to dismiss a notification.
+Here is an example that shows targeting a {@code notification_key}:
GoogleCloudMessaging gcm = GoogleCloudMessaging.get(context);
String to = NOTIFICATION_KEY;
@@ -125,17 +163,21 @@ data.putString("hello", "world");
gcm.send(to, id, data);
-This call generates the necessary XMPP stanza for sending the message. The Bundle data consists of a key-value pair.
+This call generates the necessary XMPP stanza for sending the message. The
+Bundle data consists of a key-value pair.
-For a complete example, see Getting Started.
+ For a complete example, see Implementing GCM Client.
- Response formats
+Response Formats
-This section shows examples of the responses that can be returned for notification key operations.
+This section shows examples of the responses that can be returned for
+notification key operations.
-Response for create/add/remove operations
+Create/add/remove operations
-When you make a request to create a {@code notification_key} or to add/remove its the wayregIDs, a successful response always returns the notification_key . This is the {@code notification_key} you will use for sending messages:
+When you make a request to create a {@code notification_key} or to add/remove its
+regIDs, a successful response always returns the notification_key .
+his is the {@code notification_key} you will use for sending messages:
HTTP status: 200
{
@@ -143,18 +185,23 @@ gcm.send(to, id, data);
}
-Response for send operations
+Send operations
-For a send operation that has a {@code notification_key} as its target, the possible responses are success, partial success, and failure.
+For a send operation that has a {@code notification_key} as its target, the
+possible responses are success, partial success, and failure.
-Here is an example of "success"—the {@code notification_key} has 2 regIDs associated with it, and the message was successfully sent to both of them:
+Here is an example of "success"—the {@code notification_key} has 2 regIDs
+associated with it, and the message was successfully sent to both of them:
{
"success": 2,
"failure": 0
}
-Here is an example of "partial success"—the {@code notification_key} has 3 regIDs associated with it. The message was successfully send to 1 of the regIDs, but not to the other 2. The response message lists the regIDs that failed to receive the message:
+Here is an example of "partial success"—the {@code notification_key} has
+3 regIDs associated with it. The message was successfully send to 1 of the regIDs,
+but not to the other 2. The response message lists the regIDs that failed to
+receive the message:
{
"success":1,
@@ -165,7 +212,9 @@ gcm.send(to, id, data);
]
}
-In the case of failure, the response has HTTP code 503 and no JSON. When a message fails to be delivered to one or more of the regIDs associated with a {@code notification_key}, the 3rd-party server should retry.
+In the case of failure, the response has HTTP code 503 and no JSON. When a message
+fails to be delivered to one or more of the regIDs associated with a {@code notification_key},
+the 3rd-party server should retry.
diff --git a/docs/html/google/gcm/server.jd b/docs/html/google/gcm/server.jd
index 92a1531cdbb49d146ffc4e5a807d6341ff9627fd..b5e6b480d057ac917b48a1fe971642a534a5d2be 100644
--- a/docs/html/google/gcm/server.jd
+++ b/docs/html/google/gcm/server.jd
@@ -1,36 +1,34 @@
-page.title=GCM Server
+page.title=Implementing GCM Server
@jd:body
+ The server side of GCM consists of 2 components:
+
+- Google-provided GCM Connection Servers
+take messages from a 3rd-party application server and send them to a GCM-enabled
+Android application (the "client app") running on a device. For example,
+Google provides connection servers for
+HTTP and CCS (XMPP).
+- A 3rd-party application server that you must implement. This application
+server sends data to a GCM-enabled Android application via the chosen GCM connection server.
+
+
+ Here are the basic steps you follow to implement your 3rd-party app server:
- This document gives examples of GCM server-side code for HTTP. For an example of an XMPP server (Cloud Connection Server), see Getting Started. Note that a full GCM implementation requires a client-side implementation, in addition to the server. For a complete working example that includes client and server-side code, see Getting Started.
-
- Requirements
- For the web server:
- - Ant 1.8 (it might work with earlier versions, but it's not guaranteed).
- - One of the following:
-
- - A running web server compatible with Servlets API version 2.5, such as Tomcat 6 or Jetty, or
- - Java App Engine SDK version 1.6 or later.
+ - Decide which GCM connection server(s) you want to use. Note that if you want to use
+ upstream messaging from your client applications, you must use CCS. For a more detailed
+ discussion of this, see
+ Choosing a GCM Connection Server.
+ - Decide how you want to implement your app server. For example:
+
+ - If you decide to use the HTTP connection server, you can use the
+GCM server helper library and demo app to help in implementing your app server.
+ - If you decide to use the XMPP connection server, you can use
+the provided Python or Java
+Smack demo apps as a starting point.
+ - Note that Google AppEngine does not support connections to CCS.
+
+
- - A Google account registered to use GCM.
- - The API key for that account.
- For the Android application:
-
- - Emulator (or device) running Android 2.2 with Google APIs.
- - The Google API project number of the account registered to use GCM.
-
- Setting Up GCM
- Before proceeding with the server and client setup, it's necessary to register a Google account with the Google API Console, enable Google Cloud Messaging in GCM, and obtain an API key from the Google API Console.
- For instructions on how to set up GCM, see Getting Started.
+ A full GCM implementation requires both a client implementation and a server
+implementation. For more
+information about implementing the client side, see
+Implementing GCM Client.
- Setting Up an HTTP Server
- This section describes the different options for setting up an HTTP server.
- Using a standard web server
- To set up the server using a standard, servlet-compliant web server:
-
- - From the open source site, download the following directories:
gcm-server , samples/gcm-demo-server , and samples/gcm-demo-appengine .
+Choosing a GCM Connection Server
+Currently GCM provides two connection servers:
+HTTP and CCS (XMPP). You can use them
+separately or in tandem. CCS messaging differs from GCM HTTP messaging in the following ways:
+
+ - Upstream/Downstream messages
+
+ - GCM HTTP: Downstream only: cloud-to-device.
+ - CCS: Upstream and downstream (device-to-cloud, cloud-to-device).
+
+
+ - Asynchronous messaging
+
+ - GCM HTTP: 3rd-party app servers send messages as HTTP POST requests and
+wait for a response. This mechanism is synchronous and causes the sender to block
+before sending another message.
+ - CCS: 3rd-party app servers connect to Google infrastructure using a
+persistent XMPP connection and send/receive messages to/from all their devices
+at full line speed. CCS sends acknowledgment or failure notifications (in the
+form of special ACK and NACK JSON-encoded XMPP messages) asynchronously.
+
+
- - In a text editor, edit the
samples/gcm-demo-server/WebContent/WEB-INF/classes/api.key and replace the existing text with the API key obtained above.
- - In a shell window, go to the
samples/gcm-demo-server directory.
- - Generate the server's WAR file by running
ant war :
-
- $ ant war
+ - JSON
+
+ - GCM HTTP: JSON messages sent as HTTP POST.
+ - CCS: JSON messages encapsulated in XMPP messages.
+
+
+
-Buildfile:build.xml
+Role of the 3rd-party Application Server
-init:
- [mkdir] Created dir: build/classes
- [mkdir] Created dir: dist
+Before you can write client Android applications that use the GCM feature, you must
+have an application server that meets the following criteria:
-compile:
- [javac] Compiling 6 source files to build/classes
+
+ - Able to communicate with your client.
+ - Able to fire off properly formatted requests to the GCM server.
+ - Able to handle requests and resend them as needed, using
+exponential back-off.
+ - Able to store the API key and client registration IDs. The
+API key is included in the header of POST requests that send
+messages.
+ - Able to store the API key and client registration IDs.
+ - Able to generate message IDs to uniquely identify each message it sends.
+
-war:
- [war] Building war: dist/gcm-demo.war
+Sending Messages
-BUILD SUCCESSFUL
-Total time: 0 seconds
-
-
- - Deploy the
dist/gcm-demo.war to your running server. For instance, if you're using Jetty, copy gcm-demo.war to the webapps directory of the Jetty installation.
- - Open the server's main page in a browser. The URL depends on the server you're using and your machine's IP address, but it will be something like
http://192.168.1.10:8080/gcm-demo/home , where gcm-demo is the application context and /home is the path of the main servlet.
-
-
+Here is the general sequence of events that occurs when a 3rd-party application
+server sends a message:
+
+ - The application server sends a message to GCM servers.
+ - Google enqueues and stores the message in case the device is offline.
+ - When the device is online, Google sends the message to the device.
+ - On the device, the system broadcasts the message to the specified Android
+application via Intent broadcast with proper permissions, so that only the targeted
+ndroid application gets the message. This wakes the Android application up.
+The Android application does not need to be running beforehand to receive the message.
+ - The Android application processes the message.
-Note: You can get the IP by running ifconfig on Linux or MacOS, or ipconfig on Windows.
- You server is now ready.
+The following sections describe the basic requirements for
+sending messages.
-Using App Engine for Java
+Target
+Required. When your app server sends a message in GCM, it must specify a target.
+For HTTP you must specify the target as one of:
+
+registration_ids : For sending to 1 more more devices (up to 1000).
+When you send a message to multiple registration IDs, that is called a multicast message.
+notification_key : For sending to multiple devices owned by a single user.
+
+For CCS (XMPP):
+
+- You must specify the target as the "to" field, where the "to"
+field may contain a single registration ID or a notification key.
+CCS does not support multicast messaging.
+
+Payload
+Optional. If you are including a payload in the message, you use the data
+parameter to include the payload. This applies for both HTTP and CCS.
+
+Message parameters
+
+The following table lists the parameters that a 3rd-party app server might
+include in the JSON messages it sends to a connection server. See the "Where Supported"
+column for information about which connection servers support that particular
+parameter.
+
+
+ Table 1. Message parameters.
+
+
+
+ Field |
+ Description |
+Where Supported |
+
+ to |
+In CCS, used in place of registration_ids to specify the
+recipient of a message. Its value must be a registration ID.
+The value is a string. Required. |
+CCS |
+
+
+message_id |
+In CCS, uniquely identifies a message in an XMPP connection. The value is a
+string that uniquely identifies the associated message. The value is a string. Required. |
+CCS |
+
+
+message_type |
+In CCS, indicates a special status message, typically sent by the system.
+However, your app server also uses this parameter to send an 'ack' or 'nack'
+message back to the CCS connection server. For more discussion of this topic, see
+Cloud Connection Server. The value is a string. Optional. |
+CCS |
+
+ registration_ids |
+ A string array with the list of devices (registration IDs) receiving the
+message. It must contain at least 1 and at most 1000 registration IDs. To send a
+multicast message, you must use JSON. For sending a single message to a single
+device, you could use a JSON object with just 1 registration id, or plain text
+(see below). A request must include a recipient—this can be either a
+registration ID, an array of registration IDs, or a {@code notification_key}.
+Required. |
+HTTP |
+
+
+ notification_key |
+ A string that maps a single user to multiple registration IDs associated
+with that user. This allows a 3rd-party server to send a single message to
+multiple app instances (typically on multiple devices) owned by a single user.
+A 3rd-party server can use {@code notification_key} as the target for a message
+instead of an individual registration ID (or array of registration IDs). The maximum
+number of members allowed for a {@code notification_key} is 10. For more discussion
+of this topic, see User Notifications. Optional.
+ |
+HTTP. This feature is supported in CCS, but you use it by
+specifying a notification key in the "to" field. |
+
+
+ collapse_key |
+ An arbitrary string (such as "Updates Available") that is used
+to collapse a group of like messages
+when the device is offline, so that only the last message gets sent to the
+client. This is intended to avoid sending too many messages to the phone when it
+comes back online. Note that since there is no guarantee of the order in which
+messages get sent, the "last" message may not actually be the last
+message sent by the application server. Collapse keys are also called
+send-to-sync messages.
+
+Note: GCM allows a maximum of 4 different collapse keys to be
+used by the GCM server
+at any given time. In other words, the GCM server can simultaneously store 4
+different send-to-sync messages per device, each with a different collapse key.
+If you exceed
+this number GCM will only keep 4 collapse keys, with no guarantees about which
+ones they will be. See Advanced Topics for more
+discussion of this topic. Optional. |
+CCS, HTTP |
+
+
+ data |
+ A JSON object whose fields represents the key-value pairs of the message's
+payload data. If present, the payload data it will be
+included in the Intent as application data, with the key being the extra's name.
+For instance, "data":{"score":"3x1"} would result in an intent extra
+named score whose value is the string 3x1 .
+There is no limit on the number of key/value pairs, though there is a limit on
+the total size of the message (4kb). The values could be any JSON object, but we
+recommend using strings, since the values will be converted to strings in the GCM
+server anyway. If you want to include objects or other non-string data types
+(such as integers or booleans), you have to do the conversion to string yourself.
+Also note that the key cannot be a reserved word (from or any word
+starting with google. ). To complicate things slightly, there are
+some reserved words (such as collapse_key ) that are technically
+allowed in payload data. However, if the request also contains the word, the
+value in the request will overwrite the value in the payload data. Hence using
+words that are defined as field names in this table is not recommended, even in
+cases where they are technically allowed. Optional. |
+CCS, HTTP |
+
+
+ delay_while_idle |
+ If included, indicates that the message should not be sent immediately
+if the device is idle. The server will wait for the device to become active, and
+then only the last message for each collapse_key value will be
+sent. The default value is false , and must be a JSON boolean. Optional. |
+CCS, HTTP |
+
+
+ time_to_live |
+ How long (in seconds) the message should be kept on GCM storage if the
+device is offline. Optional (default time-to-live is 4 weeks, and must be set as
+a JSON number). |
+CCS, HTTP |
+
+
+ restricted_package_name |
+ A string containing the package name of your application. When set, messages
+will only be sent to registration IDs that match the package name. Optional.
+ |
+HTTP |
+
+
+ dry_run |
+ If included, allows developers to test their request without actually
+sending a message. Optional. The default value is false , and must
+be a JSON boolean.
+ |
+HTTP |
+
+
+
+If you want to test your request (either JSON or plain text) without delivering
+the message to the devices, you can set an optional HTTP or JSON parameter called
+dry_run with the value true . The result will be almost
+identical to running the request without this parameter, except that the message
+will not be delivered to the devices. Consequently, the response will contain fake
+IDs for the message and multicast fields.
+
+Plain text (HTTP only)
+
+If you are using plain text instead of JSON, the message fields must be set as
+HTTP parameters sent in the body, and their syntax is slightly different, as
+described below:
+
+
+ Field |
+ Description |
+
+
+ registration_id |
+ Must contain the registration ID of the single device receiving the message.
+Required. |
+
+
+ collapse_key |
+ Same as JSON (see previous table). Optional. |
+
+
+ data.<key> |
+
+ Payload data, expressed as parameters prefixed with data. and
+suffixed as the key. For instance, a parameter of data.score=3x1 would
+result in an intent extra named score whose value is the string
+3x1 . There is no limit on the number of key/value parameters, though
+there is a limit on the total size of the message. Also note that the key cannot
+be a reserved word (from or any word starting with
+google. ). To complicate things slightly, there are some reserved words
+(such as collapse_key ) that are technically allowed in payload data.
+However, if the request also contains the word, the value in the request will
+overwrite the value in the payload data. Hence using words that are defined as
+field names in this table is not recommended, even in cases where they are
+technically allowed. Optional. |
+
+
+
+ delay_while_idle |
+ Should be represented as 1 or true for
+true , anything else for false . Optional. The default
+value is false . |
+
+
+ time_to_live |
+ Same as JSON (see previous table). Optional. |
+
+
+ restricted_package_name |
+ Same as JSON (see previous table). Optional.
+ |
+
+
+ dry_run |
+ Same as JSON (see previous table). Optional.
+ |
+
+
+
+Receiving Messages
+
+This is the sequence of events that occurs when an Android application
+installed on a mobile device receives a message:
-To set up the server using a standard App Engine for Java:
- - Get the files from the open source site, as described above.
-
- - In a text editor, edit
samples/gcm-demo-appengine/src/com/google/android/gcm/demo/server/ApiKeyInitializer.java and replace the existing text with the API key obtained above.
-
- Note: The API key value set in that class will be used just once to create a persistent entity on App Engine. If you deploy the application, you can use App Engine's Datastore Viewer to change it later.
-
-
- - In a shell window, go to the
samples/gcm-demo-appengine directory.
- - Start the development App Engine server by
ant runserver , using the -Dsdk.dir to indicate the location of the App Engine SDK and -Dserver.host to set your server's hostname or IP address:
-
-
-$ ant -Dsdk.dir=/opt/google/appengine-java-sdk runserver -Dserver.host=192.168.1.10
-Buildfile: gcm-demo-appengine/build.xml
-
-init:
- [mkdir] Created dir: gcm-demo-appengine/dist
-
-copyjars:
-
-compile:
-
-datanucleusenhance:
- [enhance] DataNucleus Enhancer (version 1.1.4) : Enhancement of classes
- [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=28 ms, enhance=0 ms, total=28 ms. Consult the log for full details
- [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details
-
-runserver:
- [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.jetty.JettyLogger info
- [java] INFO: Logging to JettyLogger(null) via com.google.apphosting.utils.jetty.JettyLogger
- [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.config.AppEngineWebXmlReader readAppEngineWebXml
- [java] INFO: Successfully processed gcm-demo-appengine/WebContent/WEB-INF/appengine-web.xml
- [java] Jun 15, 2012 8:46:06 PM com.google.apphosting.utils.config.AbstractConfigXmlReader readConfigXml
- [java] INFO: Successfully processed gcm-demo-appengine/WebContent/WEB-INF/web.xml
- [java] Jun 15, 2012 8:46:09 PM com.google.android.gcm.demo.server.ApiKeyInitializer contextInitialized
- [java] SEVERE: Created fake key. Please go to App Engine admin console, change its value to your API Key (the entity type is 'Settings' and its field to be changed is 'ApiKey'), then restart the server!
- [java] Jun 15, 2012 8:46:09 PM com.google.appengine.tools.development.DevAppServerImpl start
- [java] INFO: The server is running at http://192.168.1.10:8080/
- [java] Jun 15, 2012 8:46:09 PM com.google.appengine.tools.development.DevAppServerImpl start
- [java] INFO: The admin console is running at http://192.168.1.10:8080/_ah/admin
-
-
- - Open the server's main page in a browser. The URL depends on the server you're using and your machine's IP address, but it will be something like
http://192.168.1.10:8080/home , where /home is the path of the main servlet.
-
- Note: You can get the IP by running ifconfig on Linux or MacOS, or ipconfig on Windows.
-
+ - The system receives the incoming message and extracts the raw key/value
+pairs from the message payload, if any.
+ - The system passes the key/value pairs to the targeted Android application
+in a
com.google.android.c2dm.intent.RECEIVE Intent as a set of
+extras.
+ - The Android application extracts the raw data
+from the
com.google.android.c2dm.intent.RECEIVE Intent
+by key and processes the data.
- You server is now ready.
-
+See the documentation for each connection server for more detail on how it
+handles responses.
diff --git a/docs/html/google/google_toc.cs b/docs/html/google/google_toc.cs
index 999c44e1734e88fceab59c458db0fc41fbdd09ff..2ded2862b89e20019f826f07ad0fd894d03de2f9 100644
--- a/docs/html/google/google_toc.cs
+++ b/docs/html/google/google_toc.cs
@@ -37,7 +37,22 @@
-
+
+ -
+
+
+
+ -
+
-
@@ -118,29 +133,32 @@
-
-
+
-
+ -
+ Overview
+
-
Getting Started
- -
- Architectural Overview
+
-
+ Implementing GCM Client
- -
- Cloud Connection Server
+
-
+
-
User Notifications
- -
- GCM Client
-
- -
- GCM Server
-
-
Advanced Topics
diff --git a/docs/html/google/index.jd b/docs/html/google/index.jd
index ce30bce8ba41f3771d8993536e1f3095e7ef24e2..37a35fcb4452251572093b1f4618570945a7ac88 100644
--- a/docs/html/google/index.jd
+++ b/docs/html/google/index.jd
@@ -112,7 +112,7 @@ cloud messaging.
-
Provide fast and easy checkout in your app when selling physical goods and services.
Increase conversions by streamlining your purchase flow and reducing the amount of
@@ -136,11 +136,11 @@ interaction patterns, and much more.
-
+
-
- Display ads from AdMob offer you an alternative revenue opportunity that leverages
+
+ Display ads from Google Mobile Ads offer you an alternative revenue opportunity that leverages
multiple ad networks with targeted ads and several display formats.
diff --git a/docs/html/google/play-services/ads.jd b/docs/html/google/play-services/ads.jd
new file mode 100644
index 0000000000000000000000000000000000000000..c666ce9a049f8bbad0dca885b1bef147d48cf5b0
--- /dev/null
+++ b/docs/html/google/play-services/ads.jd
@@ -0,0 +1,85 @@
+page.title=Google Mobile Ads
+page.tags="Ads","monetization", "AdMob", "Google Play services"
+header.hide=1
+
+@jd:body
+
+
+
+
+
+
+
+
+
+ Google Mobile Ads
+
+ Monetize your app with banner or interstitial ads from
+ Google's vast pool of advertisers. Integrate with top ad networks through
+ mediation to maximize your revenue from impressions. Take advantage of new
+ ad features and capabilities through Google Play services, without having to
+ add or update a library in your APK.
+
+
+
+ Check out the Google Mobile Ads API reference and visit developers.google.com/mobile-ads-sdk
+ for more information about integrating Google Mobile Ads into your app.
+
+
+
+
+
+
+ Key Developer Features
+
+ Seamless auto-updates
+ The Google Mobile Ads SDK is part of Google Play services, so you can take advantage of features
+ and capabilities in each new release of Google Play services, without needing to update your
+ APK.
+
+ Monetize your apps
+ Connect with over a million Google advertisers and show relevant ads in your
+ app. Users engage with the ads, you make money.
+
+ Learn more
+ and sign up.
+
+ Innovative ads
+ Choose from a range of ads across mobile devices and tablets, including interactive
+ ad units.
+
+ Flexible and powerful tools
+ Filters and controls help you manage your ads. If you want to use multiple ad
+ networks, you can do that too, with free ad network mediation.
+
+
+
+ Getting Started
+ 1. Get the Google Play services SDK
+ The Google Mobile Ads APIs are part of the Google Play services platform.
+ To get started, set up
+ the Google Play services SDK.
+
+ 2. Run the sample
+ Once you've installed the Google Play services package, the Google Mobile Ads sample is located
+ in <android-sdk>/extras/google-play-services/samples/ads and shows you how to
+ serve banner and interstitial ads using the Google Mobile Ads APIs.
+
+ 3. Read the documentation
+ Read the AdSense
+ Terms of Service and the AdMob
+ publisher guidelines and policies.
+ For quick access while developing your Android apps, the Google Mobile Ads API reference is available here on
+ developer.android.com.
+ Detailed documentation for Google Mobile Ads for Android is available with the rest of the Google
+ Mobile Ads developer documents at developers.google.com/mobile-ads-sdk.
+ Note: The SDK doesn’t currently support DFP, Ad Exchange or Search
+ Ads for Mobile Apps but support is coming soon.
+
+
\ No newline at end of file
diff --git a/docs/html/google/play-services/id.jd b/docs/html/google/play-services/id.jd
new file mode 100644
index 0000000000000000000000000000000000000000..664c6e6f43c7b8cffc0eb468739f09513d1b7cdb
--- /dev/null
+++ b/docs/html/google/play-services/id.jd
@@ -0,0 +1,198 @@
+page.title=Advertising ID
+page.tags="Ads","Advertising ID", "ID"
+header.hide=1
+
+@jd:body
+
+
+
+
+
+ Advertising ID
+ The advertising ID is a user-specific, unique,
+ resettable ID for advertising, provided by Google Play services. It gives
+ users better controls and provides developers with a simple, standard system
+ to continue to monetize your apps. It is an anonymous identifier for advertising
+ purposes and enables users to reset their identifier or opt out of interest-based
+ ads within Google Play apps.
+
+ The advertising ID is accessible through a straightforward API that you can implement in your apps. For details,
+take a look at the
+overview and the advertising ID API reference.
+
+
+
+
+
+
+ Key Developer Features
+
+ Standard, simple ID
+ The advertising ID is a part of a standard, simple system for serving ads and performing analytics.
+
+ Giving users control
+ Users can reset their advertising ID or opt out of interest-based ads at any time, right from the Google Settings app.
+ Their preferences apply across all ad companies that use the advertising ID.
+
+
+
+
+ Getting Started
+ 1. Get the Google Play services SDK
+ The advertising ID APIs are part of the Google Play services platform.
+ To get started, set up
+ the Google Play services SDK.
+
+ 2. Read the docs and example code
+ Once you've installed the Google Play services package, review the overview
+ below, as well as the example.
+
+ For detailed documentation, take a look at the
+ advertising ID API reference documentation.
+
+
+
+
+
+Using the Advertising ID
+
+
+ The advertising ID is a unique but
+ user-resettable string identifier that lets ad networks and other apps anonymously
+ identify a user. The user's advertising ID is made available to apps through APIs
+ provided in Google Play services.
+
+
+ Users can reset their advertising ID at any time, right from the Ads section of the
+ Google Settings app on their devices. From the same app, users can also
+ opt-out of targeted advertising based on the advertising ID by setting the appropriate
+ ad tracking preference. When the
+ user opts-out of targeted ads, this ad tracking preference is made available
+ to apps through a Google Play services API.
+
+
+ Apps making use of the advertising ID must check for and respect the
+ user's ad tracking preference. Also please note that any use of the advertising ID
+ must abide by the terms of the Google Play
+ Developer Content Policies.
+
+
+
+
+
+ Google Play services APIs expose the user's advertising ID as a string format of UUID,
+ with values similar to this:
+
+“38400000-8cf0-11bd-b23e-10b96e40000d”
+
+Requirements
+
+
+ - The advertising ID APIs are supported in Google Play services 4.0+
+ - Support for the advertising ID on specific devices is based on their installed versions
+ of Google Play services
+
+
+Obtaining the user's advertising ID and ad tracking preference
+
+
+ If you want to use the advertising ID in your app, you must first install the Google
+ Play services SDK. As noted in the requirements above, you should install the
+ SDK for Google Play services 4.0 or higher if you will develop using the advertising ID
+ APIs. For information about how to get started, see Set Up Google Play services.
+
+
+ The advertising ID APIs are available in the
+ com.google.android.gms.ads.identifier package in the Google
+ Play Services library. To obtain the user's advertising ID and tracking preference,
+ call the method
+
+ getAdvertisingIdInfo() , which returns an
+ AdvertisingIdClient.Info encapsulating the user's current Advertising ID
+ and tracking preference.
+
+
+
+ Note: The getAdvertisingIdInfo() method is a
+ blocking call, so you must not call it on the main (UI) thread. If called on
+ the main thread, the method throws IllegalStateException .
+
+
+
+ Once you've retrieved the AdvertisingIdClient.Info object, you
+ can use it's
+ getId() and
+ isLimitAdTrackingEnabled() methods to access the advertising ID and
+ ad tracking preference.
+
+
+
+
+Method |
+Description |
+
+
+public String getId() |
+Retrieves the advertising ID. |
+
+
+public boolean isLimitAdTrackingEnabled() |
+Retrieves whether the user has limit ad tracking enabled or not. |
+
+
+
+
+ The advertising ID APIs do not include a "reset" method. Only users can initiate a
+ reset of their own advertising IDs, through the Google Settings application.
+
+
+ For more information about the advertising ID APIs, see the
+ reference documentation.
+
+
+Example implementation
+
+
+ Here's a basic illustration of how you can retrieve the user's advertising ID and ad
+ tracking preference in your app:
+
+
+
+import com.google.android.gms.ads.identifier.AdvertisingIdClient;
+import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
+import com.google.android.gms.common.GooglePlayServicesAvailabilityException;
+import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
+import java.io.IOException;
+...
+
+// Do not call this function from the main thread. Otherwise,
+// an IllegalStateException will be thrown.
+public void getIdThread() {
+
+ Info adInfo = null;
+ try {
+ adInfo = AdvertisingIdClient.getAdvertisingIdInfo(mContext);
+
+ } catch (IOException e) {
+ // Unrecoverable error connecting to Google Play services (e.g.,
+ // the old version of the service doesn't support getting AdvertisingId).
+
+ } catch (GooglePlayServicesAvailabilityException e) {
+ // Encountered a recoverable error connecting to Google Play services.
+
+ } catch (GooglePlayServicesNotAvailableException e) {
+ // Google Play services is not available entirely.
+ }
+ final String id = adInfo.getId();
+ final boolean isLAT = adInfo.isLimitAdTrackingEnabled();
+}
\ No newline at end of file
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index 4d38850c6750109bb08efcf211fc4ef28a2f3fb4..5c8c63b4da6082c1f3c9f3de90276071011e2458 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -1,6 +1,24 @@
-page.title=Setup Google Play Services SDK
+page.title=Set Up Google Play Services SDK
@jd:body
+
+
+
+
+
To develop an app using the Google
Play services APIs, you must download the Google Play services SDK
@@ -10,7 +28,7 @@ The download includes the client library and code samples.
To test your app when using the Google Play services SDK, you must use either:
- A compatible Android
- device that runs Android 2.2 or higher and includes Google Play Store.
+ device that runs Android 2.3 or higher and includes Google Play Store.
- The Android emulator with an AVD
that runs the Google APIs platform based on Android 4.2.2 or higher.
@@ -24,9 +42,12 @@ both phones and tablets.
To install the Google Play services SDK for development:
- - Launch the SDK Manager.
+
- Launch the SDK Manager in one of the following ways:
- - From Eclipse (with ADT),
+
- In Android Studio, click SDK Manager
+
+in the toolbar.
+ - In Eclipse (with ADT),
select Window > Android SDK Manager.
- On Windows, double-click the
SDK Manager.exe file at the root of the Android
SDK directory.
@@ -36,19 +57,25 @@ both phones and tablets.
- Install the Google Play services SDK.
Scroll to the bottom of the package list, expand Extras, select
- Google Play services, and install it.
+ Google Play services, and install it. If you're using Android Studio, also install
+ Google Repository (it provides the Maven repository used for Gradle builds).
The Google Play services SDK is saved in your Android SDK environment at
<android-sdk>/extras/google/google_play_services/ .
+
+Note: Google Play services 4.0.30 (released
+November 2013) and newer versions require Android 2.3 or higher. If your app supports Android 2.2,
+you can continue development with the Google Play services SDK, but must instead install
+Google Play services for Froyo from the SDK Manager.
+
- Install a compatible version of the Google APIs platform.
If you want to test your app on the emulator, expand the directory for Android 4.2.2
(API 17) or a higher version, select Google APIs, and install it. Then create a
new AVD with Google APIs as
the platform target.
- Note: Only Android 4.2.2 and higher versions of the
- Google APIs platform include Google Play services.
- Make a copy of the Google Play services library project.
+
Note: If you are using Android Studio, skip this step.
Copy the library project at
<android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib/
to the location where you maintain your Android app projects.
@@ -60,33 +87,102 @@ both phones and tablets.
-Set Up a Project with the Library
+Set Up a Project that Uses Google Play Services
-To set up a project to use the Google Play services SDK:
+Using Android Studio:
- - Reference the library project in your Android project.
-
See the
- Referencing a Library Project for Eclipse
- or Referencing a Library Project on the Command Line
- for more information on how to do this.
- Note:
- You should be referencing a copy of the library that you copied to your development
- workspace—you should not reference the library directly from the Android SDK directory.
+ - Open the
build.gradle file inside your application directory.
+ - Add a new build rule under
dependencies for the latest version of
+play-services . For example:
+
+apply plugin: 'android'
+...
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:+'
+ compile 'com.google.android.gms:play-services:4.0.30'
+}
+
+Be sure you update this version number each time Google Play services is updated.
+
+ - Save the changes and click Sync Project with Gradle Files
+
+in the toolbar.
+
+ - Open your app's manifest file and add the following tag as a child of the {@code <application>}
+element:
+
+<meta-data android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+
- - If you are using ProGuard, add the following
- lines in the
<project_directory>/proguard-project.txt file
- to prevent ProGuard from stripping away required classes:
+
+
+You can now begin developing features with the
+Google Play services APIs.
+
+
+Using Eclipse or another IDE:
+
+To make the Google Play services APIs available to your app, you must reference the library
+project you created in step 4 of the installation instructions.
+See the Referencing a Library Project for Eclipse or Referencing a
+Library Project on the Command Line for more information on how to do this.
+
+Note:
+You should be referencing a copy of the library that you copied to your development
+workspace—you should not reference the library directly from the Android SDK directory.
+
+After you've added the Google Play services library as a dependency for your app project,
+open your app's manifest file and add the following tag as a child of the {@code <application>}
+element:
+
+<meta-data android:name="com.google.android.gms.version"
+ android:value="@integer/google_play_services_version" />
+
+
+
+Once you've set up your project to reference the library project,
+you can begin developing features with the
+Google Play services APIs.
+
+
+
+Create a Proguard Exception
+
+To prevent ProGuard from stripping away
+required classes, add the following lines in the
+<project_directory>/proguard-project.txt file:
-keep class * extends java.util.ListResourceBundle {
protected Object[][] getContents();
}
+
+-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
+ public static final *** NULL;
+}
+
+-keepnames @com.google.android.gms.common.annotation.KeepName class *
+-keepclassmembernames class * {
+ @ccom.google.android.gms.common.annotation.KeepName *;
+}
+
+-keepnames class * implements android.os.Parcelable {
+ public static final ** CREATOR;
+}
+
+Note: When using Android Studio, you must add Proguard
+to your gradle.build file's build types. For more information, see the
+Gradle Plugin User Guide.
-Once you have the Google Play services library project added to your app project,
-you can begin developing features with the
-Google Play services APIs.
@@ -178,4 +274,4 @@ a user's device:
to display an error message to the user, which allows the user to download the APK
from the Google Play Store or enable it in the device's system settings.
-
\ No newline at end of file
+
diff --git a/docs/html/google/play-services/wallet.jd b/docs/html/google/play-services/wallet.jd
new file mode 100644
index 0000000000000000000000000000000000000000..b9b98d93e71fd86fdbdc54196c9047d98ba7e4ed
--- /dev/null
+++ b/docs/html/google/play-services/wallet.jd
@@ -0,0 +1,86 @@
+page.title=Google Wallet Instant Buy for Android
+page.tags="Wallet","payments","Instant Buy"
+header.hide=1
+
+@jd:body
+
+
+
+ Google Wallet Instant Buy
+ Add fast, secure checkout for users buying physical goods and
+ services from your app. Transactions are monitored for fraud 24/7. Keep your existing
+ payments infrastructure and integrate Google Wallet quickly, easily and free of charge.
+
+ Apply for
+ production access before launching Instant Buy in your app. We recommend applying
+ before starting development. Note that Instant Buy is currently only available to
+ US-based merchants.
+
+ Check out the Instant
+ Buy API reference and visit
+ developers.google.com/wallet/instant-buy/
+ for complete information about integrating Google Wallet Instant Buy into your app.
+
+
+
+
+
+
+
+
+
+ Key Developer Features
+
+ Add the "Buy with Google" button
+ Easily embed a “Buy with Google” button in your flow to let customers purchase instantly
+ from your app. Customers can grant you access to their payment information with just
+ a few clicks.
+
+ Add a "Buy with Google" button.
+
+ Streamline Purchases with Google+ Sign-On
+ For users ready to purchase, you can simplify the login and account creation steps
+ by adding Google+ sign in. Users can sign in with a single click and share their
+ profile information during the purchase.
+
+ Add Google+ Sign-In for Wallet.
+
+ Minimize User Data Entry
+ Google Wallet provides auto-completion of addresses, minimizing user data entry. You can also
+ retrieve billing and shipping addresses directly from the user’s Wallet to-do form pre-fills.
+ Get
+ billing addresses.
+
+
+
+
+ Getting Started
+ 1. Get the Google Play services SDK
+ The Google Wallet Android APIs are part of the Google Play services platform.
+ To get started, set up
+ the Google Play services SDK. Then see the tutorial
+ to learn how to set up your app.
+
+ 2. Run the sample
+ Once you've installed the Google Play services package, try the Google Wallet
+ sample located in <android-sdk>/extras/google-play-services/samples/wallet .
+ The sample shows you how to use the major components of the Instant Buy API.
+ The Quick
+ Start guide provides directions on how to get the Wallet sample up and running
+ 3. Read the documentation
+ For quick access while developing your Android apps, the Google Wallet
+ API reference is available here on developer.android.com.
+
+ Detailed documentation for the Instant Buy API is available at developers.google.com/wallet/instant-buy/
+
+
+
+
\ No newline at end of file
diff --git a/docs/html/google/play/billing/gp-purchase-status-api.jd b/docs/html/google/play/billing/gp-purchase-status-api.jd
index 25ef28b102ac9dde8c1431d1e5f9f996f6df6d44..4d80680705443949aa3b31f93fa0d394a0753632 100644
--- a/docs/html/google/play/billing/gp-purchase-status-api.jd
+++ b/docs/html/google/play/billing/gp-purchase-status-api.jd
@@ -58,7 +58,7 @@ made with In-app Billing v1 or v2.
The Purchase Status API is part of the Google Play Android
-Developer API v1.1, available through the Google APIs console. The new version
+Developer API v1.1, available through the Google Cloud Console. The new version
of the API supersedes the v1 API, which is deprecated. If you are using the v1
API, please migrate your operations to the v1.1 API as soon as possible.
@@ -66,7 +66,7 @@ API, please migrate your operations to the v1.1 API as soon as possible.
Using the API
To use the API, you must first register a project at the Google APIs Console and receive
+href="https://cloud.google.com/console">Google Cloud Console and receive
a Client ID and shared secret that your app will present when calling the
API. All calls are authenticated with OAuth 2.0.
@@ -93,9 +93,9 @@ application). This should provide enough access for normal
subscription-validation needs, assuming that you follow the recommendation in
this section.
- If you need to request a higher limit for your application, please use the
-“Request more” link in the Google APIs Console.
+ If you need to request a higher limit for your application, see the
+instructions in the Google Cloud Console Help.
Also, please read the section below on design best practices for minimizing your
use of the API.
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 21d295a7ecff3c520c8533abcb3b916fd145be89..8f2795a4ba08aa26d76e0cb5e599f7a4d7b4aa4c 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -63,6 +63,9 @@
-
Contacts Provider
+ -
+ Storage Access Framework
+
-
@@ -405,6 +408,7 @@
-
@@ -486,15 +490,15 @@
Web Apps
- -
- Overview
-
-
- Targeting Screens from Web Apps
+ Supporting Different Screens in Web Apps
-
Building Web Apps in WebView
+ -
+ Migrating to WebView in Android 4.4
+
-
Debugging Web Apps
diff --git a/docs/html/guide/topics/connectivity/nfc/hce.jd b/docs/html/guide/topics/connectivity/nfc/hce.jd
new file mode 100644
index 0000000000000000000000000000000000000000..4ef6859870969267586ef1dfb6529ff7c08e9657
--- /dev/null
+++ b/docs/html/guide/topics/connectivity/nfc/hce.jd
@@ -0,0 +1,618 @@
+page.title=Host-based Card Emulation
+page.tags="host card emulation", "hce","HostApduService","OffHostApduService","tap and pay"
+
+@jd:body
+
+
+
+
+
+
+Many Android-powered devices that offer NFC functionality already support NFC card
+emulation. In most cases, the card is emulated by a separate
+chip in the device, called a secure element. Many SIM cards provided by
+wireless carriers also contain a secure element.
+
+Android 4.4 introduces an additional method of card emulation that does not
+involve a secure element, called host-based card emulation. This allows any
+Android application to emulate a card and talk directly to the NFC reader. This
+document describes how host-based card emulation (HCE) works on Android and how you
+can develop an app that emulates an NFC card using this technique.
+
+
+Card Emulation with a Secure Element
+
+When NFC card emulation is provided using a secure element, the card to be emulated
+is provisioned into the secure element on
+the device through an Android application. Then, when the user holds the
+device over an NFC terminal, the NFC controller in the device routes all data
+from the reader directly to the secure element. Figure 1 illustrates this concept.
+
+
+Figure 1. NFC card emulation with a secure element.
+
+The secure element itself performs the communication with the NFC terminal,
+and no Android application is involved in the transaction at all. After the
+transaction is complete, an Android application can query the secure element
+directly for the transaction status and notify the user.
+
+
+Host-based Card Emulation
+
+When an NFC card is emulated using host-based card emulation, the data is routed to
+the host CPU on which Android applications are running directly, instead of routing the NFC
+protocol frames to a secure element. Figure 2 illustrates how host-based card emulation
+works.
+
+
+Figure 2. NFC card emulation without a secure element.
+
+
+Supported NFC Cards and Protocols
+
+
+
+The NFC standards offer support for many different protocols, and there are
+different types of cards that can be emulated.
+
+Android 4.4 supports several protocols that are common in the
+market today. Many existing contactless cards are already based on these
+protocols, such as contactless payment cards. These protocols are also
+supported by many NFC readers in the market today, including Android NFC
+devices functioning as readers themselves (see the {@link android.nfc.tech.IsoDep} class).
+This allows you to build and deploy an end-to-end NFC solution
+around HCE using only Android-powered devices.
+
+Specifically, Android 4.4 supports emulating cards that are based on the
+NFC-Forum ISO-DEP specification (based on ISO/IEC 14443-4) and process
+Application Protocol Data Units (APDUs) as defined in the ISO/IEC 7816-4
+specification. Android mandates emulating ISO-DEP only on top of the
+Nfc-A (ISO/IEC 14443-3 Type A) technology. Support for Nfc-B (ISO/IEC 14443-4
+Type B) technology is optional. The layering of all these specifications is
+shown in the figure 3.
+
+
+
+HCE Services
+
+The HCE architecture in Android is based around Android {@link android.app.Service} components
+(known as "HCE services").
+One of the key advantages of a service is that it can run in the background without
+any user interface. This is a natural fit for many HCE applications like loyalty or transit cards,
+with which the user shouldn't need to launch the app to use it.
+Instead, tapping the device against the NFC reader starts the correct service (if not already
+running) and executes the transaction in the background. Of course, you are free
+to launch additional UI (such as user notifications) from your service if that makes
+sense.
+
+
+
+Service selection
+
+When the user taps a device to an NFC reader, the Android system needs to
+ know which HCE service the NFC reader actually wants to talk to.
+This is where the ISO/IEC 7816-4 specification comes in: it defines a way to
+select applications, centered around an Application ID (AID). An AID
+consists of up to 16 bytes. If you are emulating cards for an existing NFC reader
+infrastructure, the AIDs that those readers are looking for are typically
+well-known and publicly registered (for example, the AIDs of payment networks
+such as Visa and MasterCard).
+
+If you want to deploy new reader infrastructure for your own application, you
+will need to register your own AID(s). The registration procedure for AIDs is
+defined in the ISO/IEC 7816-5 specification. Google recommends registering an
+AID as per 7816-5 if you are deploying a HCE application for Android, as it will avoid
+collisions with other applications.
+
+
+AID groups
+
+In some cases, an HCE service may need to register multiple AIDs to implement a
+certain application, and it needs to be sure that it is the default handler for
+all of these AIDs (as opposed to some AIDs in the group going to another
+service).
+
+An AID group is a list of AIDs that should be considered as belonging together
+by the OS. For all AIDs in an AID group, Android guarantees one of the
+following:
+
+
+- All AIDs in the group are routed to this HCE service
+- No AIDs in the group are routed to this HCE service (for example, because the user
+preferred another service which requested one or more AIDs in your group as
+well)
+
+
+In other words, there is no in-between state, where some AIDs in the group can
+be routed to one HCE service, and some to another.
+
+AID groups and categories
+
+Each AID group can be associated with a category. This allows Android to group
+HCE services together by category, and that in turn allows the user to set
+defaults at the category level instead of the AID level. In general, avoid
+mentioning AIDs in any user-facing parts of your application: they do not mean
+anything to the average user.
+
+Android 4.4 supports two categories: {@link
+ android.nfc.cardemulation.CardEmulation#CATEGORY_PAYMENT} (covering payment
+apps) and {@link android.nfc.cardemulation.CardEmulation#CATEGORY_OTHER}
+(for all other HCE apps).
+
+
+
+Implementing an HCE Service
+
+To emulate an NFC card using host-based card emulation, you need to create
+ a {@link android.app.Service} component that handles the NFC transactions.
+
+ Checking for HCE support
+
+Your application can check whether a device supports HCE by checking for the
+{@link android.content.pm.PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} feature. You should use the
+{@code <uses-feature>} tag in the manifest of your application to declare that your app
+uses the HCE feature, and whether it is required for the app to function or not.
+
+Service implementation
+
+Android 4.4 comes with a convenience {@link android.app.Service} class that can be used as a
+basis for implementing a HCE service: the {@link android.nfc.cardemulation.HostApduService} class.
+
+The first step is therefore to extend {@link android.nfc.cardemulation.HostApduService}.
+
+
+public class MyHostApduService extends HostApduService {
+ @Override
+ public byte[] processCommandApdu(byte[] apdu, Bundle extras) {
+ ...
+ }
+ @Override
+ public void onDeactivated(int reason) {
+ ...
+ }
+}
+
+
+{@link android.nfc.cardemulation.HostApduService}
+declares two abstract methods that need to be overridden and implemented.
+
+{@link android.nfc.cardemulation.HostApduService#processCommandApdu processCommandApdu()}
+ is called whenever a NFC reader sends an Application
+Protocol Data Unit (APDU) to your service. APDUs are defined in the ISO/IEC
+7816-4 specification as well. APDUs are the application-level packets being
+exchanged between the NFC reader and your HCE service. That application-level
+protocol is half-duplex: the NFC reader will send you a command APDU, and it
+will wait for you to send a response APDU in return.
+
+Note:
+ The ISO/IEC 7816-4 specification also defines the concept of multiple logical channels,
+ where you can have multiple parallel APDU exchanges on separate logical channels. Android’s
+ HCE implementation however only supports a single logical channel, so there’s only a
+ single-threaded exchange of APDUs.
+
+
+As mentioned previously, Android uses the AID to determine which HCE service the
+reader wants to talk to. Typically, the first APDU an NFC reader sends to your
+device is a "SELECT AID" APDU; this APDU contains the AID that the reader wants
+to talk to. Android extracts that AID from the APDU, resolves it to an HCE service,
+then forwards that APDU to the resolved service.
+
+You can send a response APDU by returning the bytes of the response APDU from
+{@link android.nfc.cardemulation.HostApduService#processCommandApdu processCommandApdu()}.
+ Note that this method will be called on the main thread of
+your application, which shouldn't be blocked. So if you can't compute and return
+a response APDU immediately, return null. You can then do the necessary work on
+another thread, and use the {@link android.nfc.cardemulation.HostApduService#sendResponseApdu
+ sendResponseApdu()} method defined
+in the {@link android.nfc.cardemulation.HostApduService} class to send the response when you are done.
+
+Android will keep forwarding new APDUs from the reader to your service, until
+either:
+
+
+- The NFC reader sends another "SELECT AID" APDU, which the OS resolves to a
+different service;
+- The NFC link between the NFC reader and your device is broken.
+
+
+In both of these cases, your class's
+ {@link android.nfc.cardemulation.HostApduService#onDeactivated onDeactivated()}
+ implementation is
+called with an argument indicating which of the two happened.
+
+If you are working with existing reader infrastructure, you need to
+implement the existing application-level protocol that the readers expect in
+your HCE service.
+
+If you are deploying new reader infrastructure which you control as well, you
+can define your own protocol and APDU sequence. In general try to limit the
+amount of APDUs and the size of the data that needs to be exchanged: this makes
+sure that your users will only have to hold their device over the NFC reader for
+a short amount of time. A sane upper bound is about 1KB of data, which can
+usually be exchanged within 300ms.
+
+
+
+Service manifest declaration and AID registration
+
+Your service must be declared in the manifest as usual, but some additional
+pieces must be added to the service declaration as well.
+
+First, to tell the platform that it is a HCE service implementing a
+{@link android.nfc.cardemulation.HostApduService} interface, your service declaration must contain an
+intent filter for the {@link android.nfc.cardemulation.HostApduService#SERVICE_INTERFACE} action.
+
+Additionally, to tell the platform which AIDs groups are requested by this
+service, a {@link android.nfc.cardemulation.HostApduService#SERVICE_META_DATA}
+<meta-data> tag must be included in
+the declaration of the service, pointing to an XML resource with additional
+information about the HCE service.
+
+Finally, you must set the {@code android:exported} attribute to true, and require the
+{@code "android.permission.BIND_NFC_SERVICE"} permission in your service declaration.
+The former ensures that the service can be bound to by external applications.
+The latter then enforces that only external applications that hold the
+{@code ""android.permission.BIND_NFC_SERVICE"} permission can bind to your service. Since
+{@code ""android.permission.BIND_NFC_SERVICE"} is a system permission, this effectively
+enforces that only the Android OS can bind to your service.
+
+Here's an example of a {@link android.nfc.cardemulation.HostApduService} manifest declaration:
+
+
+<service android:name=".MyHostApduService" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
+ android:resource="@xml/apduservice"/>
+</service>
+
+
+This meta-data tag points to an {@code apduservice.xml} file. An example of such a file
+with a single AID group declaration containing two proprietary AIDs is shown
+below:
+
+
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/servicedesc"
+ android:requireDeviceUnlock="false">
+ <aid-group android:description="@string/aiddescription"
+ android:category="other">
+ <aid-filter android:name="F0010203040506"/>
+ <aid-filter android:name="F0394148148100"/>
+ </aid-group>
+</host-apdu-service>
+
+
+The <host-apdu-service> tag is required to contain a <android:description>
+attribute that contains a user-friendly description of the service that may be
+shown in UI. The <requireDeviceUnlock> attribute can be used to specify that the
+device must be unlocked before this service can be invoked to handle APDUs.
+
+The <host-apdu-service> must contain one or more <aid-group> tags. Each
+<aid-group> tag is required to contain a android:description attribute that
+contains a user-friendly description of the AID group that may be shown in UI.
+Each <aid-group> tag must also have the android:category attribute set to
+indicate the category the AID group belongs to, e.g. the string constants
+defined by CardEmulation.CATEGORY_PAYMENT or CardEmulation.CATEGORY_OTHER. Each
+<aid-group> must contain one or more <aid-filter> tags, each of which contains a
+single AID. The AID must be specified in hexadecimal format, and contain an even
+number of characters.
+
+As a final note, your application also needs to hold the NFC permission,
+ {@link android.Manifest.permission#NFC} to be able to register as a HCE service.
+
+
+
+
+AID Conflict Resolution
+
+Multiple {@link android.nfc.cardemulation.HostApduService} components
+ may be installed on a single device, and the same AID
+can be registered by more than one service. The Android platform resolves AID
+conflicts depending on which category an AID belongs to. Each category may have
+a different conflict resolution policy.
+
+For example, for some categories (like payment) the user may be able to select a
+default service in the Android settings UI. For other categories, the policy may
+be to always ask the user which service is to be invoked in case of conflict. To
+query the conflict resolution policy for a certain category, see
+{@link android.nfc.cardemulation.CardEmulation#getSelectionModeForCategory
+ getSelectionModeForCategory()}.
+
+Checking if your service is the default
+
+Applications can check whether their HCE service is the default service for a
+certain category by using the
+{@link android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory} API.
+
+If your service is not the default, you can request it to be made the default.
+See {@link android.nfc.cardemulation.CardEmulation#ACTION_CHANGE_DEFAULT}.
+
+
+
+Payment Applications
+
+Android considers HCE services that have declared an AID group with the
+"payment" category as payment applications. The Android 4.4 release contains a
+top-level Settings menu entry called "tap & pay", which enumerates all such
+payment applications. In this settings menu, the user can select the default
+payment application that will be invoked when a payment terminal is tapped.
+
+Required assets for payment applications
+
+To provide a more visually attractive user experience, HCE payment applications
+are required to provide an additional asset for their service: a so-called
+service banner.
+
+This asset should be sized 260x96 dp, and can be specified in your meta-data XML
+file by adding the android:apduServiceBanner attribute to the
+<host-apdu-service> tag, which points to the drawable resource. An example is
+shown below:
+
+
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/servicedesc"
+ android:requireDeviceUnlock="false"
+ android:apduServiceBanner="@drawable/my_banner">
+ <aid-group android:description="@string/aiddescription"
+ android:category="payment">
+ <aid-filter android:name="F0010203040506"/>
+ <aid-filter android:name="F0394148148100"/>
+ </aid-group>
+</host-apdu-service>
+
+
+
+
+Screen Off and Lock-screen Behavior
+
+Current Android implementations turn the NFC controller and the application
+processor off completely when the screen of the device is turned off. HCE
+services will therefore not work when the screen is off.
+
+HCE services can function from the lock-screen however: this is controlled by
+the android:requireDeviceUnlock attribute in the <host-apdu-service> tag of your
+HCE service. By default, device unlock is not required, and your service will be
+invoked even if the device is locked.
+
+If you set the <android:requireDeviceUnlock attribute to "true" for your HCE
+service, Android will prompt the user to unlock the device when you tap an NFC
+reader that selects an AID that is resolved to your service. After unlocking,
+Android will show a dialog prompting the user to tap again to complete the
+transaction. This is necessary because the user may have moved the device away
+from the NFC reader in order to unlock it.
+
+
+Coexistence with Secure Element Cards
+
+This section is of interest for developers that have deployed an application
+that relies on a secure element for card emulation. Android's HCE implementation
+is designed to work in parallel with other methods of implementing card
+emulation, including the use of secure elements.
+
+Note: Android does not offer APIs for directly communicating with a secure element itself.
+
+This coexistence is based on a principle called "AID routing": the NFC
+controller keeps a routing table that consists of a (finite) list of routing
+rules. Each routing rule contains an AID and a destination. The destination can
+either be the host CPU (where Android apps are running), or a connected secure
+element.
+
+When the NFC reader sends an APDU with a "SELECT AID", the NFC controller parses
+it and checks whether the AIDs matchesNo converter for: FOOTNOTE with any AID in
+its routing table. If it matches, that APDU and all APDUs following it will be
+sent to the destination associated with the AID, until another "SELECT AID" APDU
+is received or the NFC link is broken.
+
+Note:
+ While ISO/IEC 7816-4 defines the concept of “partial matches” as well, this is currently not supported by Android HCE devices.
+
+This architecture is illustrated in figure 4.
+
+
+
+Figure 4. Android operating with both secure element
+and host-card emulation.
+
+
+The NFC controller typically also contains a default route for APDUs. When an
+AID is not found in the routing table, the default route is used. Beginning with Android
+4.4, the default route is required to be set to the host CPU. This
+means that the routing table typically only contains entries for AIDs that need
+to go to a secure element.
+
+Android applications that implement a HCE service or that use a secure element
+don't have to worry about configuring the routing table - that is taking care of
+by Android automatically. Android merely needs to know which AIDs can be handled
+by HCE services and which ones can be handled by the secure element. Based on
+which services are installed and which the user has configured as preferred, the
+routing table is configured automatically.
+
+We've already described how to declare AIDs for HCE services. The following
+section explains how to declare AIDs for applications that use a secure element
+for card emulation.
+
+
+Secure element AID registration
+
+Applications using a secure element for card emulation can declare a so-called
+"off host service" in their manifest. The declaration of such a service is
+almost identical to the declaration of a HCE service. The exceptions are:
+
+
+- The action used in the intent-filter must be set to
+{@link android.nfc.cardemulation.OffHostApduService#SERVICE_INTERFACE}
+- The meta-data name attribute must be set to
+{@link android.nfc.cardemulation.OffHostApduService#SERVICE_META_DATA}
+The meta-data XML file must use the <offhost-apdu-service> root tag
+
+
+<service android:name=".MyOffHostApduService" android:exported="true"
+ android:permission="android.permission.BIND_NFC_SERVICE">
+ <intent-filter>
+ <action android:name="android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE"/>
+ </intent-filter>
+ <meta-data android:name="android.nfc.cardemulation.off_host_apdu_ervice"
+ android:resource="@xml/apduservice"/>
+</service>
+
+
+
+
+An example of the corresponding {@code apduservice.xml} file registering two AIDs:
+
+
+<offhost-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/servicedesc">
+ <aid-group android:description="@string/subscription" android:category="other">
+ <aid-filter android:name="F0010203040506"/>
+ <aid-filter android:name="F0394148148100"/>
+ </aid-group>
+</offhost-apdu-service>
+
+
+The android:requireDeviceUnlock attribute does not apply to off host services,
+because the host CPU is not involved in the transaction and therefore cannot
+prevent the secure element from executing transactions when the device is
+locked.
+
+The android:apduServiceBanner attribute must be used for off host services that
+are payment applications as well in order to be selectable as a default payment
+application.
+
+Off host service invocation
+
+Android itself will never start or bind to a service that is declared as "off
+host". This is because the actual transactions are executed by the secure
+element and not by the Android service itself. The service declaration merely
+allows applications to register AIDs present on the secure element.
+
+HCE and Security
+
+The HCE architecture itself provides one core piece of security: because your
+service is protected by the {@link android.Manifest.permission#BIND_NFC_SERVICE}
+ system permission, only the OS can
+bind to and communicate with your service. This ensures that any APDU you
+receive is actually an APDU that was received by the OS from the NFC controller,
+and that any APDU you send back will only go to the OS, which in turn directly
+forwards the APDUs to the NFC controller.
+
+The core remaining piece is where you get the data from that you're sending back
+to the NFC reader. This is intentionally decoupled in the HCE design: it does
+not care where the data comes from, it just makes sure that it is safely
+transported to the NFC controller and out to the NFC reader.
+
+For securely storing and retrieving the data that you want to send from your HCE
+service, you can for example rely on the Android Application Sandbox, which
+isolates your app's data from other apps. For more details on Android security,
+read
+Security Tips
+.
+
+Protocol parameters and details
+
+This section is of interest for developers that want to understand what protocol
+parameters HCE devices use during the anti-collision and activations phases of
+the NFC protocols. This allows them to build a reader infrastructure that is
+compatible with Android HCE devices.
+
+Nfc-A (ISO/IEC 14443 type A) protocol anti-collision and activation
+
+As part of the Nfc-A protocol activation, multiple frames are exchanged.
+
+In the first part of the exchange the HCE device will present its UID; HCE
+devices should be assumed to have a random UID. This means that on every tap,
+the UID that is presented to the reader will be a randomly generated UID.
+Because of this, NFC readers should not depend on the UID of HCE devices as a
+form of authentication or identification.
+
+The NFC reader can subsequently select the HCE device by sending a SEL_REQ
+command. The SEL_RES response of the HCE device will at least have the 6th bit
+(0x20) set, indicating that the device supports ISO-DEP. Note that other bits in
+the SEL_RES may be set as well, indicating for example support for the NFC-DEP
+(p2p) protocol. Since other bits may be set, readers wanting to interact with
+HCE devices should explicitly check for the 6th bit only, and not compare the
+complete SEL_RES with a value of 0x20.
+
+ISO-DEP activation
+
+After the Nfc-A protocol is activated, the ISO-DEP protocol activation is
+initiated by the NFC reader. It sends a "RATS" (Request for Answer To Select)
+command. The RATS response, the ATS, is completely generated by the NFC
+controller and not configurable by HCE services. However, HCE implementations
+are required to meet NFC Forum requirements for the ATS response, so NFC readers
+can count on these parameters being set in accordance with NFC Forum
+requirements for any HCE device.
+
+The section below provides more details on the individual bytes of the ATS
+response provided by the NFC controller on a HCE device:
+
+
+- TL: length of the ATS response. Must not indicate a length greater than 20
+bytes.
+- T0: bits 5, 6 and 7 must be set on all HCE devices, indicating TA(1), TB(1)
+and TC(1) are included in the ATS response. Bits 1 to 4 indicate the FSCI,
+coding the maximum frame size. On HCE devices the value of FSCI must be
+between 0h and 8h.
+- T(A)1: defines bitrates between reader and emulator, and whether they can be
+asymmetric. There are no bitrate requirements or guarantees for HCE devices.
+- T(B)1: bits 1 to 4 indicate the Start-up Frame Guard time Integer (SFGI). On
+HCE devices, SFGI must be <= 8h. Bits 5 to 8 indicate the Frame Waiting time
+Integer (FWI) and codes the Frame Waiting Time (FWT). On HCE devices, FWI must
+be <= 8h.
+- T(C)1: bit 5 indicates support for "Advanced Protocol features". HCE devices
+may or may not support "Advanced Protocol features". Bit 2 indicates support
+for DID. HCE devices may or may not support DID. Bit 1 indicates support for
+NAD. HCE devices must not support NAD and set bit 1 to zero.
+- Historical bytes: HCE devices may return up to 15 historical bytes. NFC
+readers willing to interact with HCE services should make no assumptions about
+the contents of the historical bytes or their presence.
+
+
+Note that many HCE devices are likely made compliant with protocol requirements
+that the payment networks united in EMVCo have specified in their "Contactless
+Communication Protocol" specification. In particular:
+
+
+- FSCI in T0 must be between 2h and 8h.
+- T(A)1 must be set to 0x80, indicating only the 106 kbit/s bitrate is
+supported, and asymmetric bitrates between reader and emulator are not
+supported.
+- FWI in T(B)1 must be <= 7h.
+
+
+APDU data exchange
+
+As noted earlier, HCE implementations only support a single logical channel.
+Attempting to select applications on different logical channels will not work on
+a HCE device.
diff --git a/docs/html/guide/topics/connectivity/nfc/index.jd b/docs/html/guide/topics/connectivity/nfc/index.jd
index 88c206f4958a2d470894de84404524527b8a1105..f12facf75ecc2bdc8b407cd465f87aefdedebae9 100644
--- a/docs/html/guide/topics/connectivity/nfc/index.jd
+++ b/docs/html/guide/topics/connectivity/nfc/index.jd
@@ -14,6 +14,18 @@ page.title=Near Field Communication
framework APIs are based around a NFC Forum standard
called NDEF (NFC Data Exchange Format).
+Android-powered devices with NFC simultaneously support three main modes of operation:
+
+
+- Reader/writer mode, allowing the NFC device to read and/or write
+passive NFC tags and stickers.
+- P2P mode, allowing the NFC device to exchange data with other NFC
+peers; this operation mode is used by Android Beam.
+- Card emulation mode, allowing the NFC device itself to act as an NFC
+card. The emulated NFC card can then be accessed by an external NFC reader,
+such as an NFC point-of-sale terminal.
+
+
- NFC Basics
- This document describes how Android handles discovered NFC tags and how it notifies
@@ -29,5 +41,10 @@ NFC feature set of Android.
bytes using your own protocol stack. In these cases, Android provides support to detect
certain tag technologies and to open communication with the tag using your own protocol
stack.
+
+ - Host-based Card Emulation
+ - This document describes how Android devices can perform as NFC cards without using
+ a secure element, allowing any Android application to emulate a card and talk directly to
+ the NFC reader.
-
\ No newline at end of file
+
diff --git a/docs/html/guide/topics/manifest/uses-configuration-element.jd b/docs/html/guide/topics/manifest/uses-configuration-element.jd
index 810975e1e0df668b3cfb79118ca9e51eae662836..e9a0ba43081f525ad5e6575bfbbd68d19c60df6f 100644
--- a/docs/html/guide/topics/manifest/uses-configuration-element.jd
+++ b/docs/html/guide/topics/manifest/uses-configuration-element.jd
@@ -6,18 +6,18 @@ parent.link=manifest-intro.html
+easier to update the doc when the change is made... Nov 2013, this still seems unresolved. -->
- syntax:
<uses-configuration
- android:reqFiveWayNav=["true" | "false"]
+ android:reqFiveWayNav=["true" | "false"]
android:reqHardKeyboard=["true" | "false"]
android:reqKeyboardType=["undefined" | "nokeys" | "qwerty" | "twelvekey"]
android:reqNavigation=["undefined" | "nonav" | "dpad" | "trackball" | "wheel"]
@@ -27,38 +27,35 @@ easier to update the doc when the change is made. -->
<manifest>
- description:
-- Indicates what hardware and software features the application requires.
-For example, an application might specify that it requires a physical keyboard
+
- Indicates what hardware and software features the application requires.
+For example, an application might specify that it requires a physical keyboard
or a particular navigation device, like a trackball. The specification is
used to avoid installing the application on devices where it will not work.
-
-If an application can work with different device configurations, it
-should include separate {@code <uses-configuration>} declarations for
-each one. Each declaration must be complete. For example, if an application
-requires a five-way navigation control, a touch screen that can be operated
-with a finger, and either a standard QWERTY keyboard or a numeric 12-key
-keypad like those found on most phones, it would specify these requirements
-with two {@code <uses-configuration>} elements as follows:
-
+Note: Most apps should not use this manifest tag. You should
+always support input with a directional pad (d-pad) in order to assist sight-impaired
+users and support devices that provide d-pad input in addition to or instead of touch. For
+information about how to support d-pad input in your app, read Enabling Focus Navigation. If
+your app absolutely cannot function without a touchscreen, then instead use the {@code <uses-feature>} tag to
+declare the required touchscreen type, ranging from {@code "android.hardware.faketouch"} for basic
+touch-style events to more advanced touch types such as {@code
+"android.hardware.touchscreen.multitouch.jazzhand"} for distinct input from multiple fingers.
-<uses-configuration android:reqFiveWayNav="true" android:reqTouchScreen="finger"
- android:reqKeyboardType="qwerty" />
-<uses-configuration android:reqFiveWayNav="true" android:reqTouchScreen="finger"
- android:reqKeyboardType="twelvekey" />
- attributes:
- {@code android:reqFiveWayNav}
-- Whether or not the application requires a five-way navigation control
+
- Whether or not the application requires a five-way navigation control
— "{@code true}" if it does, and "{@code false}" if not. A five-way
-control is one that can move the selection up, down, right, or left, and
-also provides a way of invoking the current selection. It could be a
-D-pad (directional pad), trackball, or other device.
+control is one that can move the selection up, down, right, or left, and
+also provides a way of invoking the current selection. It could be a
+D-pad (directional pad), trackball, or other device.
If an application requires a directional control, but not a control of a
-particular type, it can set this attribute to "{@code true}" and ignore
+particular type, it can set this attribute to "{@code true}" and ignore
the reqNavigation attribute. However,
if it requires a particular type of directional control, it can ignore
this attribute and set {@code reqNavigation} instead.
@@ -69,10 +66,10 @@ this attribute and set {@code reqNavigation} instead.
"{@code true}" if it does, and "{@code false}" if not.
- {@code android:reqKeyboardType}
-- The type of keyboard the application requires, if any at all.
-This attribute does not distinguish between hardware and software
+
- The type of keyboard the application requires, if any at all.
+This attribute does not distinguish between hardware and software
keyboards. If a hardware keyboard of a certain type is required,
-specify the type here and also set the {@code reqHardKeyboard} attribute
+specify the type here and also set the {@code reqHardKeyboard} attribute
to "{@code true}".
@@ -85,8 +82,8 @@ The value must be one of the following strings:
Description |
|
"{@code undefined}" |
- The application does not require a keyboard.
- (A keyboard requirement is not defined.)
+ | The application does not require a keyboard.
+ (A keyboard requirement is not defined.)
This is the default value. |
"{@code nokeys}" |
@@ -96,14 +93,14 @@ The value must be one of the following strings:
The application requires a standard QWERTY keyboard. |
"{@code twelvekey}" |
- The application requires a twelve-key keypad, like those on most
- phones — with keys for the digits from {@code 0} through
+ | The application requires a twelve-key keypad, like those on most
+ phones — with keys for the digits from {@code 0} through
{@code 9} plus star ({@code *}) and pound ({@code #}) keys. |
{@code android:reqNavigation}
- The navigation device required by the application, if any. The value
+The navigation device required by the application, if any. The value
must be one of the following strings:
@@ -112,8 +109,8 @@ must be one of the following strings:
Description |
"{@code undefined}" |
- The application does not require any type of navigation control.
- (The navigation requirement is not defined.)
+ | The application does not require any type of navigation control.
+ (The navigation requirement is not defined.)
This is the default value. |
"{@code nonav}" |
@@ -132,14 +129,14 @@ must be one of the following strings:
If an application requires a navigational control, but the exact type of
-control doesn't matter, it can set the
+control doesn't matter, it can set the
reqFiveWayNav attribute to "{@code true}"
rather than set this one.
{@code android:reqTouchScreen}
The type of touch screen the application requires, if any at all.
-The value must be one of the following strings:
+The value must be one of the following strings:
@@ -147,7 +144,7 @@ The value must be one of the following strings:
Description |
"{@code undefined}" |
- The application doesn't require a touch screen.
+ | The application doesn't require a touch screen.
(The touch screen requirement is undefined.)
This is the default value. |
@@ -158,7 +155,14 @@ The value must be one of the following strings:
The application requires a touch screen that's operated with a stylus. |
"{@code finger}" |
- The application requires a touch screen that can be operated with a finger. |
+ The application requires a touch screen that can be operated with a finger.
+
+ Note: If some type of touch input is required for your app,
+ you should instead use the
+ {@code
+ <uses-feature>} tag to declare the required touchscreen
+ type, beginning with {@code "android.hardware.faketouch"} for basic touch-style events.
+ |
@@ -172,7 +176,7 @@ The value must be one of the following strings:
diff --git a/docs/html/guide/topics/manifest/uses-permission-element.jd b/docs/html/guide/topics/manifest/uses-permission-element.jd
index 8e9e795accb0d430b4c73068af9eac795bb7fd5c..bd7091eaa1b063a186ca511b8fe8a66e22fdfa17 100644
--- a/docs/html/guide/topics/manifest/uses-permission-element.jd
+++ b/docs/html/guide/topics/manifest/uses-permission-element.jd
@@ -35,7 +35,8 @@ href="{@docRoot}guide/topics/manifest/uses-feature-element.html#permissions-feat
syntax:
-<uses-permission android:name="string" />
+<uses-permission android:name="string"
+ android:maxSdkVersion="integer" />
contained in:
<manifest>
@@ -63,6 +64,25 @@ standard system permissions, such as "{@code android.permission.CAMERA}"
or "{@code android.permission.READ_CONTACTS}". As these examples show,
a permission name typically includes the package name as a prefix.
+{@code android:maxSdkVersion}
+The highest API level at which this permission should be granted to your app.
+Setting this attribute is useful if the permission your app requires is no longer needed beginning
+at a certain API level.
+For example, beginning with Android 4.4 (API level 19), it's no longer necessary for your app
+to request the {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission when your
+app wants to write to its own application-specific directories on external storage (the directories
+provided by {@link android.content.Context#getExternalFilesDir getExternalFilesDir()}). However,
+the permission is required for API level 18 and lower. So you can declare that this
+permission is needed only up to API level 18 with a declaration such as this:
+
+<uses-permission
+ android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+ android:maxSdkVersion="18" />
+
+This way, beginning with API level 19, the system will no longer grant your app the
+{@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission.
+
+
diff --git a/docs/html/guide/topics/manifest/uses-sdk-element.jd b/docs/html/guide/topics/manifest/uses-sdk-element.jd
index 07b08f63cadbc6ca1161c8f3c97c1f69795d4390..916965854378134ecc5210280168b2a4076eed22 100644
--- a/docs/html/guide/topics/manifest/uses-sdk-element.jd
+++ b/docs/html/guide/topics/manifest/uses-sdk-element.jd
@@ -227,6 +227,12 @@ Versions dashboards page.
Platform Version | API Level | VERSION_CODE | Notes |
+ Android 4.4 |
+ 19 |
+ {@link android.os.Build.VERSION_CODES#KITKAT} |
+ Platform
+Highlights |
+
Android 4.3 |
18 |
{@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} |
diff --git a/docs/html/guide/topics/providers/document-provider.jd b/docs/html/guide/topics/providers/document-provider.jd
new file mode 100644
index 0000000000000000000000000000000000000000..9af8d5ae616ab64b2b4fe1dfac16f9e986d3693a
--- /dev/null
+++ b/docs/html/guide/topics/providers/document-provider.jd
@@ -0,0 +1,875 @@
+page.title=Storage Access Framework
+@jd:body
+
+Android 4.4 (API level 19) introduces the Storage Access Framework. The
+Storage Access Framework encapsulates capabilities in the Android platform that
+allow apps to request files from file storage services. The Storage Access
+Framework includes the following:
+
+
+- Document provider—A content provider that allows a
+storage service (such as Google Drive) to reveal the files it manages. This is
+implemented as a subclass of the {@link android.provider.DocumentsProvider} class.
+The document provider schema is based on a traditional file hierarchy,
+though how your document provider physically stores data is up to you.
+The Android platform includes several built-in document providers, such as
+Downloads, Images, and Videos.
+
+- Client app—A custom app that invokes the
+{@link android.content.Intent#ACTION_OPEN_DOCUMENT} and/or
+{@link android.content.Intent#ACTION_CREATE_DOCUMENT} intent and receives the
+files returned by document providers.
+
+- Picker—A system UI that lets users access documents from all
+document providers that satisfy the client app's search criteria.
+
+
+Some of the features offered by the Storage Access Framework are as follows:
+
+- Lets users browse content from all document providers, not just a single app.
+- Makes it possible for your app to have long term, persistent access to
+ documents owned by a document provider. Through this access users can add, edit,
+ save, and delete files on the provider.
+- Supports multiple user accounts and transient roots such as USB storage
+providers, which only appear if the drive is plugged in.
+
+
+Overview
+
+The Storage Access Framework centers around a content provider that is a
+subclass of the {@link android.provider.DocumentsProvider} class. Within a document provider, data is
+structured as a traditional file hierarchy:
+
+Figure 1. Document provider data model. A Root points to a single Document,
+which then starts the fan-out of the entire tree.
+
+Note the following:
+
+
+- Each document provider reports one or more
+"roots" which are starting points into exploring a tree of documents.
+Each root has a unique {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID},
+and it points to a document (a directory)
+representing the contents under that root.
+Roots are dynamic by design to support use cases like multiple accounts,
+transient USB storage devices, or user login/log out.
+
+- Under each root is a single document. That document points to 1 to N documents,
+each of which in turn can point to 1 to N documents.
+
+- Each storage backend surfaces
+individual files and directories by referencing them with a unique
+{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}.
+Document IDs must be unique and not change once issued, since they are used for persistent
+URI grants across device reboots.
+
+
+- Documents can be either an openable file (with a specific MIME type), or a
+directory containing additional documents (with the
+{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR} MIME type).
+
+- Each document can have different capabilities, as described by
+{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}.
+For example, {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE},
+{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}, and
+{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}.
+The same {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} can be
+included in multiple directories.
+
+
+Control Flow
+As stated above, the document provider data model is based on a traditional
+file hierarchy. However, you can physically store your data however you like, as
+long as it can be accessed through the {@link android.provider.DocumentsProvider} API. For example, you
+could use tag-based cloud storage for your data.
+
+Figure 2 shows an example of how a photo app might use the Storage Access Framework
+to access stored data:
+
+
+Figure 2. Storage Access Framework Flow
+
+Note the following:
+
+
+- In the Storage Access Framework, providers and clients don't interact
+directly. A client requests permission to interact
+with files (that is, to read, edit, create, or delete files).
+
+- The interaction starts when an application (in this example, a photo app) fires the intent
+{@link android.content.Intent#ACTION_OPEN_DOCUMENT} or {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. The intent may include filters
+to further refine the criteria—for example, "give me all openable files
+that have the 'image' MIME type."
+
+- Once the intent fires, the system picker goes to each registered provider
+and shows the user the matching content roots.
+
+- The picker gives users a standard interface for accessing documents, even
+though the underlying document providers may be very different. For example, figure 2
+shows a Google Drive provider, a USB provider, and a cloud provider.
+
+
+Figure 3 shows a picker in which a user searching for images has selected a
+Google Drive account:
+
+
+
+Figure 3. Picker
+
+When the user selects Google Drive the images are displayed, as shown in
+figure 4. From that point on, the user can interact with them in whatever ways
+are supported by the provider and client app.
+
+
+
+Figure 4. Images
+
+Writing a Client App
+
+On Android 4.3 and lower, if you want your app to retrieve a file from another
+app, it must invoke an intent such as {@link android.content.Intent#ACTION_PICK}
+or {@link android.content.Intent#ACTION_GET_CONTENT}. The user must then select
+a single app from which to pick a file and the selected app must provide a user
+interface for the user to browse and pick from the available files.
+
+On Android 4.4 and higher, you have the additional option of using the
+{@link android.content.Intent#ACTION_OPEN_DOCUMENT} intent,
+which displays a picker UI controlled by the system that allows the user to
+browse all files that other apps have made available. From this single UI, the
+user can pick a file from any of the supported apps.
+
+{@link android.content.Intent#ACTION_OPEN_DOCUMENT} is
+not intended to be a replacement for {@link android.content.Intent#ACTION_GET_CONTENT}.
+The one you should use depends on the needs of your app:
+
+
+- Use {@link android.content.Intent#ACTION_GET_CONTENT} if you want your app
+to simply read/import data. With this approach, the app imports a copy of the data,
+such as an image file.
+
+- Use {@link android.content.Intent#ACTION_OPEN_DOCUMENT} if you want your
+app to have long term, persistent access to documents owned by a document
+provider. An example would be a photo-editing app that lets users edit
+images stored in a document provider.
+
+
+
+
+This section describes how to write client apps based on the
+{@link android.content.Intent#ACTION_OPEN_DOCUMENT} and
+{@link android.content.Intent#ACTION_CREATE_DOCUMENT} intents.
+
+
+Search for documents
+
+
+The following snippet uses {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
+to search for document providers that
+contain image files:
+
+private static final int READ_REQUEST_CODE = 42;
+...
+/**
+ * Fires an intent to spin up the "file chooser" UI and select an image.
+ */
+public void performFileSearch() {
+
+ // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
+ // browser.
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+ // Filter to only show results that can be "opened", such as a
+ // file (as opposed to a list of contacts or timezones)
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ // Filter to show only images, using the image MIME data type.
+ // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
+ // To search for all documents available via installed storage providers,
+ // it would be "*/*".
+ intent.setType("image/*");
+
+ startActivityForResult(intent, READ_REQUEST_CODE);
+}
+
+Note the following:
+
+- When the app fires the {@link android.content.Intent#ACTION_OPEN_DOCUMENT}
+intent, it launches a picker that displays all matching document providers.
+
+- Adding the category {@link android.content.Intent#CATEGORY_OPENABLE} to the
+intent filters the results to display only documents that can be opened, such as image files.
+
+- The statement {@code intent.setType("image/*")} further filters to
+display only documents that have the image MIME data type.
+
+
+Process Results
+
+Once the user selects a document in the picker,
+{@link android.app.Activity#onActivityResult onActivityResult()} gets called.
+The URI that points to the selected document is contained in the {@code resultData}
+parameter. Extract the URI using {@link android.content.Intent#getData getData()}.
+Once you have it, you can use it to retrieve the document the user wants. For
+example:
+
+@Override
+public void onActivityResult(int requestCode, int resultCode,
+ Intent resultData) {
+
+ // The ACTION_OPEN_DOCUMENT intent was sent with the request code
+ // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
+ // response to some other intent, and the code below shouldn't run at all.
+
+ if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ // The document selected by the user won't be returned in the intent.
+ // Instead, a URI to that document will be contained in the return intent
+ // provided to this method as a parameter.
+ // Pull that URI using resultData.getData().
+ Uri uri = null;
+ if (resultData != null) {
+ uri = resultData.getData();
+ Log.i(TAG, "Uri: " + uri.toString());
+ showImage(uri);
+ }
+ }
+}
+
+
+
+
+Once you have the URI for a document, you gain access to its metadata. This
+snippet grabs the metadata for a document specified by the URI, and logs it:
+
+public void dumpImageMetaData(Uri uri) {
+
+ // The query, since it only applies to a single document, will only return
+ // one row. There's no need to filter, sort, or select fields, since we want
+ // all fields for one document.
+ Cursor cursor = getActivity().getContentResolver()
+ .query(uri, null, null, null, null, null);
+
+ try {
+ // moveToFirst() returns false if the cursor has 0 rows. Very handy for
+ // "if there's anything to look at, look at it" conditionals.
+ if (cursor != null && cursor.moveToFirst()) {
+
+ // Note it's called "Display Name". This is
+ // provider-specific, and might not necessarily be the file name.
+ String displayName = cursor.getString(
+ cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+ Log.i(TAG, "Display Name: " + displayName);
+
+ int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+ // If the size is unknown, the value stored is null. But since an
+ // int can't be null in Java, the behavior is implementation-specific,
+ // which is just a fancy term for "unpredictable". So as
+ // a rule, check if it's null before assigning to an int. This will
+ // happen often: The storage API allows for remote files, whose
+ // size might not be locally known.
+ String size = null;
+ if (!cursor.isNull(sizeIndex)) {
+ // Technically the column stores an int, but cursor.getString()
+ // will do the conversion automatically.
+ size = cursor.getString(sizeIndex);
+ } else {
+ size = "Unknown";
+ }
+ Log.i(TAG, "Size: " + size);
+ }
+ } finally {
+ cursor.close();
+ }
+}
+
+
+Open a document
+
+Once you have the URI for a document, you can open it or do whatever else
+you want to do with it.
+
+Bitmap
+
+Here is an example of how you might open a {@link android.graphics.Bitmap}:
+
+private Bitmap getBitmapFromUri(Uri uri) throws IOException {
+ ParcelFileDescriptor parcelFileDescriptor =
+ getContentResolver().openFileDescriptor(uri, "r");
+ FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+ Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
+ parcelFileDescriptor.close();
+ return image;
+}
+
+
+Note that you should not do this operation on the UI thread. Do it in the
+background, using {@link android.os.AsyncTask}. Once you open the bitmap, you
+can display it in an {@link android.widget.ImageView}.
+
+
+Get an InputStream
+
+Here is an example of how you can get an {@link java.io.InputStream} from the URI. In this
+snippet, the lines of the file are being read into a string:
+
+private String readTextFromUri(Uri uri) throws IOException {
+ InputStream inputStream = getContentResolver().openInputStream(uri);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(
+ inputStream));
+ StringBuilder stringBuilder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ stringBuilder.append(line);
+ }
+ fileInputStream.close();
+ parcelFileDescriptor.close();
+ return stringBuilder.toString();
+}
+
+
+Create a new document
+
+Your app can create a new document in a document provider using the
+{@link android.content.Intent#ACTION_CREATE_DOCUMENT}
+intent. To create a file you give your intent a MIME type and a file name, and
+launch it with a unique request code. The rest is taken care of for you:
+
+
+
+// Here are some examples of how you might call this method.
+// The first parameter is the MIME type, and the second parameter is the name
+// of the file you are creating:
+//
+// createFile("text/plain", "foobar.txt");
+// createFile("image/png", "mypicture.png");
+
+// Unique request code.
+private static final int WRITE_REQUEST_CODE = 43;
+...
+private void createFile(String mimeType, String fileName) {
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+
+ // Filter to only show results that can be "opened", such as
+ // a file (as opposed to a list of contacts or timezones).
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ // Create a file with the requested MIME type.
+ intent.setType(mimeType);
+ intent.putExtra(Intent.EXTRA_TITLE, fileName);
+ startActivityForResult(intent, WRITE_REQUEST_CODE);
+}
+
+
+Once you create a new document you can get its URI in
+{@link android.app.Activity#onActivityResult onActivityResult()}, so that you
+can continue to write to it.
+
+Delete a document
+
+If you have the URI for a document and the document's
+{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS}
+contains
+{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE},
+you can delete the document. For example:
+
+
+DocumentsContract.deleteDocument(getContentResolver(), uri);
+
+
+Edit a document
+
+You can use the Storage Access Framework to edit a text document in place.
+This snippet fires
+the {@link android.content.Intent#ACTION_OPEN_DOCUMENT} intent and uses the
+category {@link android.content.Intent#CATEGORY_OPENABLE} to to display only
+documents that can be opened. It further filters to show only text files:
+
+
+private static final int EDIT_REQUEST_CODE = 44;
+/**
+ * Open a file for writing and append some text to it.
+ */
+ private void editDocument() {
+ // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
+ // file browser.
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+
+ // Filter to only show results that can be "opened", such as a
+ // file (as opposed to a list of contacts or timezones).
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+
+ // Filter to show only text files.
+ intent.setType("text/plain");
+
+ startActivityForResult(intent, EDIT_REQUEST_CODE);
+}
+
+
+Next, from {@link android.app.Activity#onActivityResult onActivityResult()}
+(see Process results) you can call code to perform the edit.
+The following snippet gets a {@link java.io.FileOutputStream}
+from the {@link android.content.ContentResolver}. By default it uses “write” mode.
+It's best practice to ask for the least amount of access you need, so don’t ask
+for read/write if all you need is write:
+
+private void alterDocument(Uri uri) {
+ try {
+ ParcelFileDescriptor pfd = getActivity().getContentResolver().
+ openFileDescriptor(uri, "w");
+ FileOutputStream fileOutputStream =
+ new FileOutputStream(pfd.getFileDescriptor());
+ fileOutputStream.write(("Overwritten by MyCloud at " +
+ System.currentTimeMillis() + "\n").getBytes());
+ // Let the document provider know you're done by closing the stream.
+ fileOutputStream.close();
+ pfd.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+}
+
+Persist permissions
+
+When your app opens a file for reading or writing, the system gives your
+app a URI permission grant for that file. It lasts until the user's device restarts.
+But suppose your app is an image-editing app, and you want users to be able to
+access the last 5 images they edited, directly from your app. If the user's device has
+restarted, you'd have to send the user back to the system picker to find the
+files, which is obviously not ideal.
+
+To prevent this from happening, you can persist the permissions the system
+gives your app. Effectively, your app "takes" the persistable URI permission grant
+that the system is offering. This gives the user continued access to the files
+through your app, even if the device has been restarted:
+
+
+final int takeFlags = intent.getFlags()
+ & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+// Check for the freshest data.
+getContentResolver().takePersistableUriPermission(uri, takeFlags);
+
+There is one final step. You may have saved the most
+recent URIs your app accessed, but they may no longer be valid—another app
+may have deleted or modified a document. Thus, you should always call
+{@code getContentResolver().takePersistableUriPermission()} to check for the
+freshest data.
+
+Writing a Custom Document Provider
+
+
+If you're developing an app that provides storage services for files (such as
+a cloud save service), you can make your files available through the Storage
+Access Framework by writing a custom document provider. This section describes
+how to do this.
+
+
+Manifest
+
+To implement a custom document provider, add the following to your application's
+manifest:
+
+
+- A target of API level 19 or higher.
+
+- A
<provider> element that declares your custom storage
+provider.
+
+- The name of your provider, which is its class name, including package name.
+For example:
com.example.android.storageprovider.MyCloudProvider .
+
+- The name of your authority, which is your package name (in this example,
+
com.example.android.storageprovider ) plus the type of content provider
+(documents ). For example, {@code com.example.android.storageprovider.documents}.
+
+- The attribute
android:exported set to "true" .
+You must export your provider so that other apps can see it.
+
+- The attribute
android:grantUriPermissions set to
+"true" . This allows the system to grant other apps access
+to content in your provider. For a discussion of how to persist a grant for
+a particular document, see Persist permissions.
+
+- The {@code MANAGE_DOCUMENTS} permission. By default a provider is available
+to everyone. Adding this permission restricts your provider to the system,
+which is important for security.
+
+- An intent filter that includes the
+{@code android.content.action.DOCUMENTS_PROVIDER} action, so that your provider
+appears in the picker when the system searches for providers.
+
+
+Here are excerpts from a sample manifest that includes a provider:
+
+<manifest... >
+ ...
+ <uses-sdk
+ android:minSdkVersion="19"
+ android:targetSdkVersion="19" />
+ ....
+ <provider
+ android:name="com.example.android.storageprovider.MyCloudProvider"
+ android:authorities="com.example.android.storageprovider.documents"
+ android:grantUriPermissions="true"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_DOCUMENTS">
+ <intent-filter>
+ <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
+ </intent-filter>
+ </provider>
+ </application>
+
+</manifest>
+
+Supporting devices running Android 4.3 and lower
+
+The
+{@link android.content.Intent#ACTION_OPEN_DOCUMENT} intent is only available
+on devices running Android 4.4 and higher.
+If you want your application to support {@link android.content.Intent#ACTION_GET_CONTENT}
+to accommodate devices that are running Android 4.3 and lower, you should
+disable the {@link android.content.Intent#ACTION_GET_CONTENT} intent filter in
+your manifest if a device is running Android 4.4 or higher. A
+document provider and {@link android.content.Intent#ACTION_GET_CONTENT} should be considered
+ mutually exclusive. If you support both of them simultaneously, your app will
+appear twice in the system picker UI, offering two different ways of accessing
+your stored data. This would be confusing for users.
+
+Here is the recommended way of disabling the
+{@link android.content.Intent#ACTION_GET_CONTENT} intent filter for devices
+running Android version 4.4 or higher:
+
+
+- In your {@code bool.xml} resources file under {@code res/values/}, add
+this line:
<bool name="atMostJellyBeanMR2">true</bool>
+
+- In your {@code bool.xml} resources file under {@code res/values-v19/}, add
+this line:
<bool name="atMostJellyBeanMR2">false</bool>
+
+- Add an
+activity
+alias to disable the {@link android.content.Intent#ACTION_GET_CONTENT} intent
+filter for versions 4.4 (API level 19) and higher. For example:
+
+
+<!-- This activity alias is added so that GET_CONTENT intent-filter
+ can be disabled for builds on API level 19 and higher. -->
+<activity-alias android:name="com.android.example.app.MyPicker"
+ android:targetActivity="com.android.example.app.MyActivity"
+ ...
+ android:enabled="@bool/atMostJellyBeanMR2">
+ <intent-filter>
+ <action android:name="android.intent.action.GET_CONTENT" />
+ <category android:name="android.intent.category.OPENABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="image/*" />
+ <data android:mimeType="video/*" />
+ </intent-filter>
+</activity-alias>
+
+
+
+Contracts
+
+Usually when you write a custom content provider, one of the tasks is
+implementing contract classes, as described in the
+
+Content Providers developers guide. A contract class is a {@code public final} class
+that contains constant definitions for the URIs, column names, MIME types, and
+other metadata that pertain to the provider. The Storage Access Framework
+provides these contract classes for you, so you don't need to write your
+own:
+
+
+ - {@link android.provider.DocumentsContract.Document}
+ - {@link android.provider.DocumentsContract.Root}
+
+
+For example, here are the columns you might return in a cursor when
+your document provider is queried for documents or the root:
+
+private static final String[] DEFAULT_ROOT_PROJECTION =
+ new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES,
+ Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+ Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
+ Root.COLUMN_AVAILABLE_BYTES,};
+private static final String[] DEFAULT_DOCUMENT_PROJECTION = new
+ String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED,
+ Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
+
+
+Subclass DocumentsProvider
+
+The next step in writing a custom document provider is to subclass the
+abstract class {@link android.provider.DocumentsProvider}. At minimum, you need
+to implement the following methods:
+
+
+- {@link android.provider.DocumentsProvider#queryRoots queryRoots()}
+
+- {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
+
+- {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
+
+- {@link android.provider.DocumentsProvider#openDocument openDocument()}
+
+
+These are the only methods you are strictly required to implement, but there
+are many more you might want to. See {@link android.provider.DocumentsProvider}
+for details.
+
+Implement queryRoots
+
+Your implementation of {@link android.provider.DocumentsProvider#queryRoots
+queryRoots()} must return a {@link android.database.Cursor} pointing to all the
+root directories of your document providers, using columns defined in
+{@link android.provider.DocumentsContract.Root}.
+
+In the following snippet, the {@code projection} parameter represents the
+specific fields the caller wants to get back. The snippet creates a new cursor
+and adds one row to it—one root, a top level directory, like
+Downloads or Images. Most providers only have one root. You might have more than one,
+for example, in the case of multiple user accounts. In that case, just add a
+second row to the cursor.
+
+
+@Override
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+
+ // Create a cursor with either the requested fields, or the default
+ // projection if "projection" is null.
+ final MatrixCursor result =
+ new MatrixCursor(resolveRootProjection(projection));
+
+ // If user is not logged in, return an empty root cursor. This removes our
+ // provider from the list entirely.
+ if (!isUserLoggedIn()) {
+ return result;
+ }
+
+ // It's possible to have multiple roots (e.g. for multiple accounts in the
+ // same app) -- just add multiple cursor rows.
+ // Construct one row for a root called "MyCloud".
+ final MatrixCursor.RowBuilder row = result.newRow();
+ row.add(Root.COLUMN_ROOT_ID, ROOT);
+ row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));
+
+ // FLAG_SUPPORTS_CREATE means at least one directory under the root supports
+ // creating documents. FLAG_SUPPORTS_RECENTS means your application's most
+ // recently used documents will show up in the "Recents" category.
+ // FLAG_SUPPORTS_SEARCH allows users to search all documents the application
+ // shares.
+ row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
+ Root.FLAG_SUPPORTS_RECENTS |
+ Root.FLAG_SUPPORTS_SEARCH);
+
+ // COLUMN_TITLE is the root title (e.g. Gallery, Drive).
+ row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title));
+
+ // This document id cannot change once it's shared.
+ row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));
+
+ // The child MIME types are used to filter the roots and only present to the
+ // user roots that contain the desired type somewhere in their file hierarchy.
+ row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
+ row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
+ row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);
+
+ return result;
+}
+
+Implement queryChildDocuments
+
+Your implementation of
+{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}
+must return a {@link android.database.Cursor} that points to all the files in
+the specified directory, using columns defined in
+{@link android.provider.DocumentsContract.Document}.
+
+This method gets called when you choose an application root in the picker UI.
+It gets the child documents of a directory under the root. It can be called at any level in
+the file hierarchy, not just the root. This snippet
+makes a new cursor with the requested columns, then adds information about
+every immediate child in the parent directory to the cursor.
+A child can be an image, another directory—any file:
+
+@Override
+public Cursor queryChildDocuments(String parentDocumentId, String[] projection,
+ String sortOrder) throws FileNotFoundException {
+
+ final MatrixCursor result = new
+ MatrixCursor(resolveDocumentProjection(projection));
+ final File parent = getFileForDocId(parentDocumentId);
+ for (File file : parent.listFiles()) {
+ // Adds the file's display name, MIME type, size, and so on.
+ includeFile(result, null, file);
+ }
+ return result;
+}
+
+
+Implement queryDocument
+
+Your implementation of
+{@link android.provider.DocumentsProvider#queryDocument queryDocument()}
+must return a {@link android.database.Cursor} that points to the specified file,
+using columns defined in {@link android.provider.DocumentsContract.Document}.
+
+
+The {@link android.provider.DocumentsProvider#queryDocument queryDocument()}
+method returns the same information that was passed in
+{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()},
+but for a specific file:
+
+
+@Override
+public Cursor queryDocument(String documentId, String[] projection) throws
+ FileNotFoundException {
+
+ // Create a cursor with the requested projection, or the default projection.
+ final MatrixCursor result = new
+ MatrixCursor(resolveDocumentProjection(projection));
+ includeFile(result, documentId, null);
+ return result;
+}
+
+
+Implement openDocument
+
+You must implement {@link android.provider.DocumentsProvider#openDocument
+openDocument()} to return a {@link android.os.ParcelFileDescriptor} representing
+the specified file. Other apps can use the returned {@link android.os.ParcelFileDescriptor}
+to stream data. The system calls this method once the user selects a file
+and the client app requests access to it by calling
+{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}.
+For example:
+
+@Override
+public ParcelFileDescriptor openDocument(final String documentId,
+ final String mode,
+ CancellationSignal signal) throws
+ FileNotFoundException {
+ Log.v(TAG, "openDocument, mode: " + mode);
+ // It's OK to do network operations in this method to download the document,
+ // as long as you periodically check the CancellationSignal. If you have an
+ // extremely large file to transfer from the network, a better solution may
+ // be pipes or sockets (see ParcelFileDescriptor for helper methods).
+
+ final File file = getFileForDocId(documentId);
+
+ final boolean isWrite = (mode.indexOf('w') != -1);
+ if(isWrite) {
+ // Attach a close listener if the document is opened in write mode.
+ try {
+ Handler handler = new Handler(getContext().getMainLooper());
+ return ParcelFileDescriptor.open(file, accessMode, handler,
+ new ParcelFileDescriptor.OnCloseListener() {
+ @Override
+ public void onClose(IOException e) {
+
+ // Update the file with the cloud server. The client is done
+ // writing.
+ Log.i(TAG, "A file with id " +
+ documentId + " has been closed!
+ Time to " +
+ "update the server.");
+ }
+
+ });
+ } catch (IOException e) {
+ throw new FileNotFoundException("Failed to open document with id "
+ + documentId + " and mode " + mode);
+ }
+ } else {
+ return ParcelFileDescriptor.open(file, accessMode);
+ }
+}
+
+
+Security
+
+Suppose your document provider is a password-protected cloud storage service
+and you want to make sure that users are logged in before you start sharing their files.
+What should your app do if the user is not logged in? The solution is to return
+zero roots in your implementation of {@link android.provider.DocumentsProvider#queryRoots
+queryRoots()}. That is, an empty root cursor:
+
+
+public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+...
+ // If user is not logged in, return an empty root cursor. This removes our
+ // provider from the list entirely.
+ if (!isUserLoggedIn()) {
+ return result;
+}
+
+
+The other step is to call {@code getContentResolver().notifyChange()}.
+Remember the {@link android.provider.DocumentsContract}? We’re using it to make
+this URI. The following snippet tells the system to query the roots of your
+document provider whenever the user's login status changes. If the user is not
+logged in, a call to {@link android.provider.DocumentsProvider#queryRoots queryRoots()} returns an
+empty cursor, as shown above. This ensures that a provider's documents are only
+available if the user is logged into the provider.
+
+private void onLoginButtonClick() {
+ loginOrLogout();
+ getContentResolver().notifyChange(DocumentsContract
+ .buildRootsUri(AUTHORITY), null);
+}
+
diff --git a/docs/html/guide/topics/resources/localization.jd b/docs/html/guide/topics/resources/localization.jd
index 55c8dc4234caf0ce72edc4beed535f924558b1bf..7288aeb00bb239930571dd04bf8b23f785e2f4a2 100644
--- a/docs/html/guide/topics/resources/localization.jd
+++ b/docs/html/guide/topics/resources/localization.jd
@@ -19,7 +19,7 @@ parent.link=index.html
- Overview: Resource-Switching in Android
- Using Resources for Localization
-- Localization Strategies
+- Localization Tips
- Testing Localized Applications
@@ -304,7 +304,13 @@ Alternative Resources.
For more about this, see Accessing Resources.
-Localization Strategies
+Localization Checklist
+
+For a complete overview of the process of localizing and distributing an Android application,
+see the Localization
+Checklist document.
+
+Localization Tips
Design your application to work in any locale
@@ -478,6 +484,4 @@ the new locale.
portrait orientation and see if the application will run.
-Localization Checklist
-For an overview of the process of localizing an Android application, see the Localization Checklist.
diff --git a/docs/html/guide/webapps/best-practices.jd b/docs/html/guide/webapps/best-practices.jd
index 13629909276b4354b88884950845446e919cba2a..a13c69da2fba998ed86429d676042dcf130bee19 100644
--- a/docs/html/guide/webapps/best-practices.jd
+++ b/docs/html/guide/webapps/best-practices.jd
@@ -1,6 +1,24 @@
page.title=Best Practices for Web Apps
@jd:body
+
+
+
+
+
+
+
+
+
+
+Frame Alert
+
+
+
+This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client.
+
+Link to Non-frame version.
+
+ |
|
|