Merge "Additional cleanup after stack deletion." into klp-modular-dev
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 4389e3d..90d8d4c 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -535,6 +535,8 @@
 import org.jivesoftware.smack.ConnectionListener;
 import org.jivesoftware.smack.PacketInterceptor;
 import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.SmackException.NotConnectedException;
 import org.jivesoftware.smack.XMPPConnection;
 import org.jivesoftware.smack.XMPPException;
 import org.jivesoftware.smack.filter.PacketTypeFilter;
@@ -544,352 +546,378 @@
 import org.jivesoftware.smack.packet.PacketExtension;
 import org.jivesoftware.smack.provider.PacketExtensionProvider;
 import org.jivesoftware.smack.provider.ProviderManager;
+import org.jivesoftware.smack.tcp.XMPPTCPConnection;
 import org.jivesoftware.smack.util.StringUtils;
 import org.json.simple.JSONValue;
 import org.json.simple.parser.ParseException;
 import org.xmlpull.v1.XmlPullParser;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Random;
+import java.util.UUID;
 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.
+ * Sample Smack implementation of a client for GCM Cloud Connection Server. This
+ * code can be run as a standalone CCS client.
  *
  * <p>For illustration purposes only.
  */
 public class SmackCcsClient {
 
-  Logger logger = Logger.getLogger("SmackCcsClient");
+    private static final Logger logger = Logger.getLogger("SmackCcsClient");
 
-  public static final String GCM_SERVER = "gcm.googleapis.com";
-  public static final int GCM_PORT = 5235;
+    private static final String GCM_SERVER = "gcm.googleapis.com";
+    private static final int GCM_PORT = 5235;
 
-  public static final String GCM_ELEMENT_NAME = "gcm";
-  public static final String GCM_NAMESPACE = "google:mobile:data";
+    private static final String GCM_ELEMENT_NAME = "gcm";
+    private static final String GCM_NAMESPACE = "google:mobile:data";
 
-  static Random random = new Random();
-  XMPPConnection connection;
-  ConnectionConfiguration config;
+    static {
 
-  /**
-   * 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;
+        ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
+            new PacketExtensionProvider() {
+                @Override
+                public PacketExtension parseExtension(XmlPullParser parser) throws
+                        Exception {
+                    String json = parser.nextText();
+                    return new GcmPacketExtension(json);
+                }
+            });
     }
 
-    public String getJson() {
-      return json;
+    private XMPPConnection connection;
+
+    /**
+     * Indicates whether the connection is in draining state, which means that it
+     * will not accept any new downstream messages.
+     */
+    protected volatile boolean connectionDraining = false;
+
+    /**
+     * Sends a downstream message to GCM.
+     *
+     * @return true if the message has been successfully sent.
+     */
+    public boolean sendDownstreamMessage(String jsonRequest) throws
+            NotConnectedException {
+        if (!connectionDraining) {
+            send(jsonRequest);
+            return true;
+        }
+        logger.info("Dropping downstream message since the connection is draining");
+        return false;
     }
 
-    @Override
-    public String toXML() {
-      return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
-          GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
+    /**
+     * 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 nextMessageId() {
+        return "m-" + UUID.randomUUID().toString();
     }
 
-    @SuppressWarnings("unused")
-    public Packet toPacket() {
-      return new Message() {
-        // Must override toXML() because it includes a <body>
+    /**
+     * Sends a packet with contents provided.
+     */
+    protected void send(String jsonRequest) throws NotConnectedException {
+        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 properly process upstream messages.
+     */
+    protected void handleUpstreamMessage(Map<String, Object> jsonObject) {
+        // PackageName of the application that sent this message.
+        String category = (String) jsonObject.get("category");
+        String from = (String) jsonObject.get("from");
+        @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, nextMessageId(), payload,
+                "echo:CollapseKey", null, false);
+
+        try {
+            sendDownstreamMessage(echo);
+        } catch (NotConnectedException e) {
+            logger.log(Level.WARNING, "Not connected anymore, echo message is
+                    not sent", e);
+        }
+    }
+
+    /**
+     * Handles an ACK.
+     *
+     * <p>Logs a {@code INFO} message, but subclasses could override it to
+     * properly handle ACKs.
+     */
+    protected void handleAckReceipt(Map<String, Object> jsonObject) {
+        String messageId = (String) jsonObject.get("message_id");
+        String from = (String) jsonObject.get("from");
+        logger.log(Level.INFO, "handleAckReceipt() from: " + from + ",
+                messageId: " + messageId);
+    }
+
+    /**
+     * Handles a NACK.
+     *
+     * <p>Logs a {@code INFO} message, but subclasses could override it to
+     * properly handle NACKs.
+     */
+    protected void handleNackReceipt(Map<String, Object> jsonObject) {
+        String messageId = (String) jsonObject.get("message_id");
+        String from = (String) jsonObject.get("from");
+        logger.log(Level.INFO, "handleNackReceipt() from: " + from + ",
+                messageId: " + messageId);
+    }
+
+    protected void handleControlMessage(Map<String, Object> jsonObject) {
+        logger.log(Level.INFO, "handleControlMessage(): " + jsonObject);
+        String controlType = (String) jsonObject.get("control_type");
+        if ("CONNECTION_DRAINING".equals(controlType)) {
+            connectionDraining = true;
+        } else {
+            logger.log(Level.INFO, "Unrecognized control type: %s. This could
+                    happen if new features are " + "added to the CCS protocol.",
+                    controlType);
+        }
+    }
+
+    /**
+     * 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.
+     */
+        protected 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 senderId Your GCM project number
+     * @param apiKey API Key of your project
+     */
+    public void connect(long senderId, String apiKey)
+            throws XMPPException, IOException, SmackException {
+        ConnectionConfiguration config =
+                new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
+        config.setSecurityMode(SecurityMode.enabled);
+        config.setReconnectionAllowed(true);
+        config.setRosterLoadedAtLogin(false);
+        config.setSendPresence(false);
+        config.setSocketFactory(SSLSocketFactory.getDefault());
+
+        connection = new XMPPTCPConnection(config);
+        connection.connect();
+
+        connection.addConnectionListener(new LoggingConnectionListener());
+
+        // 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
+                        handleUpstreamMessage(jsonObject);
+
+                        // Send ACK to CCS
+                        String messageId = (String) jsonObject.get("message_id");
+                        String from = (String) jsonObject.get("from");
+                        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 if ("control".equals(messageType.toString())) {
+                          // Process control message
+                          handleControlMessage(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, "Failed to process packet", 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(senderId + "@gcm.googleapis.com", apiKey);
+    }
+
+    public static void main(String[] args) throws Exception {
+        final long senderId = 1234567890L; // your GCM sender id
+        final String password = "Your API key";
+
+        SmackCcsClient ccsClient = new SmackCcsClient();
+
+        ccsClient.connect(senderId, password);
+
+        // Send a sample hello downstream message to a device.
+        String toRegId = "RegistrationIdOfTheTargetDevice";
+        String messageId = ccsClient.nextMessageId();
+        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;
+        String message = createJsonMessage(toRegId, messageId, payload,
+                collapseKey, timeToLive, true);
+
+        ccsClient.sendDownstreamMessage(message);
+    }
+
+    /**
+     * XMPP Packet Extension for GCM Cloud Connection Server.
+     */
+    private static final class GcmPacketExtension extends DefaultPacketExtension {
+
+        private final String json;
+
+        public GcmPacketExtension(String json) {
+            super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
+            this.json = json;
+        }
+
+        public String getJson() {
+            return json;
+        }
+
         @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();
+            return String.format("<%s xmlns=\"%s\">%s</%s>",
+                    GCM_ELEMENT_NAME, GCM_NAMESPACE,
+                    StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
         }
-      };
-    }
-  }
 
-  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);
+        public Packet toPacket() {
+            Message message = new Message();
+            message.addExtension(this);
+            return message;
         }
-      }
-    }, 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));
-  }
+    private static final class LoggingConnectionListener
+            implements ConnectionListener {
+
+        @Override
+        public void connected(XMPPConnection xmppConnection) {
+            logger.info("Connected.");
+        }
+
+        @Override
+        public void authenticated(XMPPConnection xmppConnection) {
+            logger.info("Authenticated.");
+        }
+
+        @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.info("Connection closed on error.");
+        }
+
+        @Override
+        public void connectionClosed() {
+            logger.info("Connection closed.");
+        }
+    }
 }</pre>
+
 <h3 id="python">Python sample</h3>
 
 <p>Here is an example of a CCS app server written in Python. This sample echo
diff --git a/docs/html/google/play-services/setup.jd b/docs/html/google/play-services/setup.jd
index cc2047e..ebd3694 100644
--- a/docs/html/google/play-services/setup.jd
+++ b/docs/html/google/play-services/setup.jd
@@ -129,7 +129,7 @@
 <ol>
   <li>Copy the library project at <code>&lt;android-sdk&gt;/extras/google/google_play_services/libproject/google-play-services_lib/</code> to the location where you maintain your Android app projects.</li>
 
-  <li>In your app project, reference Google Play services library project. See
+  <li>In your app project, reference the Google Play services library project. See
     <a href="{@docRoot}tools/projects/projects-cmdline.html#ReferencingLibraryProject">Referencing
     a Library Project on the Command Line</a> for more information on how to do this.
     <p class="note"><strong>Note:</strong>
diff --git a/docs/html/google/play/billing/billing_overview.jd b/docs/html/google/play/billing/billing_overview.jd
index 301d911..12f8c9a 100644
--- a/docs/html/google/play/billing/billing_overview.jd
+++ b/docs/html/google/play/billing/billing_overview.jd
@@ -38,6 +38,16 @@
 features that you need to understand in order to add In-app 
 Billing features into your application.</p>
 
+<p class="note"><b>Note</b>: Ensure that you comply with applicable laws in the countries where you
+distribute apps. For example, in EU countries, laws based on the
+<a href="http://ec.europa.eu/justice/consumer-marketing/unfair-trade/unfair-practices/">Unfair
+Commercial Practices Directive</a> prohibit direct exhortations to children to buy advertised
+products or to persuade their parents or other adults to buy advertised products for them.
+See the
+<a href="http://ec.europa.eu/consumers/enforcement/docs/common_position_on_online_games_en.pdf">position
+of the EU consumer protection authorities</a> for more information on this and other topics.
+</p>
+
 <h2 id="api">In-app Billing API</h2>
 <p>Your application accesses the In-app Billing service using an API that is 
 exposed by the Google Play app that is installed on the device. The Google Play 
diff --git a/docs/html/tools/sdk/ndk/index.jd b/docs/html/tools/sdk/ndk/index.jd
index b77a72a..71b15d5 100644
--- a/docs/html/tools/sdk/ndk/index.jd
+++ b/docs/html/tools/sdk/ndk/index.jd
@@ -349,8 +349,7 @@
 <input id="agree" type="checkbox" name="agree" value="1" onclick="onAgreeChecked()" />
 <label id="agreeLabel" for="agree">I have read and agree with the above terms and conditions</label>
 </p>
-<p><a href="" class="button disabled ndk" id="downloadForRealz" onclick="return
-onDownloadNdkForRealz(this);"></a></p>
+<p><a href="" class="button disabled ndk" id="downloadForRealz" onclick="return onDownloadNdkForRealz(this);"></a></p>
 </div>
 
 
diff --git a/docs/html/training/wearables/data-layer/events.jd b/docs/html/training/wearables/data-layer/events.jd
index 0146c4e..a37afe0 100644
--- a/docs/html/training/wearables/data-layer/events.jd
+++ b/docs/html/training/wearables/data-layer/events.jd
@@ -43,7 +43,8 @@
     &#64;Override
     public void onResult(final DataItemResult result) {
         if(result.getStatus().isSuccess()) {
-        Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
+            Log.d(TAG, "Data item set: " + result.getDataItem().getUri());
+        }
     }
 });
 </pre>
@@ -293,7 +294,7 @@
     &#64;Override
     protected void onStop() {
         if (null != mGoogleApiClient && mGoogleApiClient.isConnected()) {
-            Wearable.NodeApi.removeListener(mGoogleApiClient, this);
+            Wearable.DataApi.removeListener(mGoogleApiClient, this);
             mGoogleApiClient.disconnect();
         }
         super.onStop();
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 610f6cf..b5f88f0 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -88,6 +88,7 @@
 import android.view.ViewRootImpl;
 import android.view.ViewStub;
 import android.view.Window;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -2094,6 +2095,22 @@
         }
 
         @Override
+        public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+            if (mOutsetBottom != null) {
+                final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+                int bottom = (int) mOutsetBottom.getDimension(metrics);
+                WindowInsets newInsets = insets.replaceSystemWindowInsets(
+                        insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+                        insets.getSystemWindowInsetRight(),
+                        insets.getSystemWindowInsetBottom() + bottom);
+                return super.dispatchApplyWindowInsets(newInsets);
+            } else {
+                return super.dispatchApplyWindowInsets(insets);
+            }
+        }
+
+
+        @Override
         public boolean onTouchEvent(MotionEvent event) {
             return onInterceptTouchEvent(event);
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5626c7a..e3e79f0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -10930,14 +10930,19 @@
         public void waitForAllWindowsDrawn(IRemoteCallback callback, long timeout) {
             synchronized (mWindowMap) {
                 mWaitingForDrawnCallback = callback;
-                final WindowList windows = getDefaultWindowListLocked();
-                for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
-                    final WindowState win = windows.get(winNdx);
-                    if (win.mHasSurface) {
-                        win.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
-                        // Force add to mResizingWindows.
-                        win.mLastContentInsets.set(-1, -1, -1, -1);
-                        mWaitingForDrawn.add(win);
+                for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+                    final WindowList windows =
+                            mDisplayContents.valueAt(displayNdx).getWindowList();
+                    for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+                        final WindowState win = windows.get(winNdx);
+                        if (win.mHasSurface) {
+                            win.mWinAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
+                            // Force add to mResizingWindows.
+                            win.mLastContentInsets.set(-1, -1, -1, -1);
+                            if (DEBUG_SCREEN_ON) Slog.d(TAG, "waitForAllWindowsDrawn: adding " +
+                                    win);
+                            mWaitingForDrawn.add(win);
+                        }
                     }
                 }
                 requestTraversalLocked();