Merge "Add GIDs to packages.list, update SD card perms." into klp-dev
diff --git a/Android.mk b/Android.mk
index 22ee3a6..656e40c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -355,6 +355,7 @@
 	frameworks/base/core/java/android/content/SyncStats.aidl \
 	frameworks/base/core/java/android/content/res/Configuration.aidl \
 	frameworks/base/core/java/android/database/CursorWindow.aidl \
+	frameworks/base/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.aidl \
 	frameworks/base/core/java/android/net/Uri.aidl \
 	frameworks/base/core/java/android/nfc/NdefMessage.aidl \
 	frameworks/base/core/java/android/nfc/NdefRecord.aidl \
@@ -391,6 +392,8 @@
 	frameworks/base/telephony/java/android/telephony/ServiceState.aidl \
 	frameworks/base/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
 	frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl \
+	frameworks/base/wifi/java/android/net/wifi/BatchedScanSettings.aidl \
+	frameworks/base/wifi/java/android/net/wifi/BatchedScanResult.aidl \
 
 gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
 $(gen): PRIVATE_SRC_FILES := $(aidl_files)
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 104fe67..8fd5186 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -168,6 +168,8 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/IPrinterDiscoveryObserver.*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/printservice/)
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/api/current.txt b/api/current.txt
index 6026cc5..6235add 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2338,17 +2338,23 @@
   public abstract class Animator implements java.lang.Cloneable {
     ctor public Animator();
     method public void addListener(android.animation.Animator.AnimatorListener);
+    method public void addPauseListener(android.animation.Animator.AnimatorPauseListener);
     method public void cancel();
     method public android.animation.Animator clone();
     method public void end();
     method public abstract long getDuration();
     method public android.animation.TimeInterpolator getInterpolator();
     method public java.util.ArrayList<android.animation.Animator.AnimatorListener> getListeners();
+    method public java.util.ArrayList<android.animation.Animator.AnimatorPauseListener> getPauseListeners();
     method public abstract long getStartDelay();
+    method public boolean isPaused();
     method public abstract boolean isRunning();
     method public boolean isStarted();
+    method public void pause();
     method public void removeAllListeners();
     method public void removeListener(android.animation.Animator.AnimatorListener);
+    method public void removePauseListener(android.animation.Animator.AnimatorPauseListener);
+    method public void resume();
     method public abstract android.animation.Animator setDuration(long);
     method public abstract void setInterpolator(android.animation.TimeInterpolator);
     method public abstract void setStartDelay(long);
@@ -2365,16 +2371,23 @@
     method public abstract void onAnimationStart(android.animation.Animator);
   }
 
+  public static abstract interface Animator.AnimatorPauseListener {
+    method public abstract void onAnimationPause(android.animation.Animator);
+    method public abstract void onAnimationResume(android.animation.Animator);
+  }
+
   public class AnimatorInflater {
     ctor public AnimatorInflater();
     method public static android.animation.Animator loadAnimator(android.content.Context, int) throws android.content.res.Resources.NotFoundException;
   }
 
-  public abstract class AnimatorListenerAdapter implements android.animation.Animator.AnimatorListener {
+  public abstract class AnimatorListenerAdapter implements android.animation.Animator.AnimatorListener android.animation.Animator.AnimatorPauseListener {
     ctor public AnimatorListenerAdapter();
     method public void onAnimationCancel(android.animation.Animator);
     method public void onAnimationEnd(android.animation.Animator);
+    method public void onAnimationPause(android.animation.Animator);
     method public void onAnimationRepeat(android.animation.Animator);
+    method public void onAnimationResume(android.animation.Animator);
     method public void onAnimationStart(android.animation.Animator);
   }
 
@@ -11569,6 +11582,18 @@
     field public static final int TEMPORARILY_UNAVAILABLE = 1; // 0x1
   }
 
+  public abstract class SettingInjectorService extends android.app.IntentService {
+    ctor public SettingInjectorService(java.lang.String);
+    method protected abstract android.location.SettingInjectorService.Status getStatus();
+    method protected final void onHandleIntent(android.content.Intent);
+  }
+
+  public static final class SettingInjectorService.Status {
+    ctor public SettingInjectorService.Status(java.lang.String, boolean);
+    field public final boolean enabled;
+    field public final java.lang.String summary;
+  }
+
 }
 
 package android.media {
@@ -12046,6 +12071,7 @@
     method public final void queueSecureInputBuffer(int, int, android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
     method public final void release();
     method public final void releaseOutputBuffer(int, boolean);
+    method public final void setParameters(java.util.Map<java.lang.String, java.lang.Object>);
     method public final void setVideoScalingMode(int);
     method public final void signalEndOfInputStream();
     method public final void start();
@@ -12059,6 +12085,9 @@
     field public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd
     field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe
     field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff
+    field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
+    field public static final java.lang.String PARAMETER_KEY_SUSPEND = "drop-input-frames";
+    field public static final java.lang.String PARAMETER_KEY_VIDEO_BITRATE = "videoBitrate";
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
     field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; // 0x2
   }
@@ -12100,6 +12129,7 @@
 
   public static final class MediaCodecInfo.CodecCapabilities {
     ctor public MediaCodecInfo.CodecCapabilities();
+    method public final boolean isFeatureSupported(java.lang.String);
     field public static final int COLOR_Format12bitRGB444 = 3; // 0x3
     field public static final int COLOR_Format16bitARGB1555 = 5; // 0x5
     field public static final int COLOR_Format16bitARGB4444 = 4; // 0x4
@@ -12146,6 +12176,7 @@
     field public static final int COLOR_FormatYUV444Interleaved = 29; // 0x1d
     field public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00
     field public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100
+    field public static final java.lang.String FEATURE_AdaptivePlayback = "adaptive-playback";
     field public int[] colorFormats;
     field public android.media.MediaCodecInfo.CodecProfileLevel[] profileLevels;
   }
@@ -18741,7 +18772,7 @@
     method public android.print.PrintDocumentInfo.Builder setPageCount(int);
   }
 
-  public final class PrintFileDocumentAdapter extends android.print.PrintDocumentAdapter {
+  public class PrintFileDocumentAdapter extends android.print.PrintDocumentAdapter {
     ctor public PrintFileDocumentAdapter(android.content.Context, java.io.File, android.print.PrintDocumentInfo);
     method public void onLayout(android.print.PrintAttributes, android.print.PrintAttributes, android.os.CancellationSignal, android.print.PrintDocumentAdapter.LayoutResultCallback, android.os.Bundle);
     method public void onWrite(android.print.PageRange[], java.io.FileDescriptor, android.os.CancellationSignal, android.print.PrintDocumentAdapter.WriteResultCallback);
@@ -18913,7 +18944,6 @@
   public abstract class PrinterDiscoverySession {
     ctor public PrinterDiscoverySession(android.content.Context);
     method public final void addPrinters(java.util.List<android.print.PrinterInfo>);
-    method public final boolean isClosed();
     method public abstract void onClose();
     method public abstract void onOpen(java.util.List<android.print.PrinterId>);
     method public abstract void onRequestPrinterUpdate(android.print.PrinterId);
@@ -25819,7 +25849,9 @@
     method public android.view.InputDevice.MotionRange getMotionRange(int, int);
     method public java.util.List<android.view.InputDevice.MotionRange> getMotionRanges();
     method public java.lang.String getName();
+    method public int getProductId();
     method public int getSources();
+    method public int getVendorId();
     method public android.os.Vibrator getVibrator();
     method public boolean isVirtual();
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 39eb8d6..89accbb 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -30,6 +30,17 @@
     ArrayList<AnimatorListener> mListeners = null;
 
     /**
+     * The set of listeners to be sent pause/resume events through the life
+     * of an animation.
+     */
+    ArrayList<AnimatorPauseListener> mPauseListeners = null;
+
+    /**
+     * Whether this animator is currently in a paused state.
+     */
+    boolean mPaused = false;
+
+    /**
      * Starts this animation. If the animation has a nonzero startDelay, the animation will start
      * running after that delay elapses. A non-delayed animation will have its initial
      * value(s) set immediately, followed by calls to
@@ -69,6 +80,66 @@
     }
 
     /**
+     * Pauses a running animation. This method should only be called on the same thread on
+     * which the animation was started. If the animation has not yet been {@link
+     * #isStarted() started} or has since ended, then the call is ignored. Paused
+     * animations can be resumed by calling {@link #resume()}.
+     *
+     * @see #resume()
+     * @see #isPaused()
+     * @see AnimatorPauseListener
+     */
+    public void pause() {
+        if (isStarted() && !mPaused) {
+            mPaused = true;
+            if (mPauseListeners != null) {
+                ArrayList<AnimatorPauseListener> tmpListeners =
+                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onAnimationPause(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * Resumes a paused animation, causing the animator to pick up where it left off
+     * when it was paused. This method should only be called on the same thread on
+     * which the animation was started. Calls to resume() on an animator that is
+     * not currently paused will be ignored.
+     *
+     * @see #pause()
+     * @see #isPaused()
+     * @see AnimatorPauseListener
+     */
+    public void resume() {
+        if (mPaused) {
+            mPaused = false;
+            if (mPauseListeners != null) {
+                ArrayList<AnimatorPauseListener> tmpListeners =
+                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onAnimationResume(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns whether this animator is currently in a paused state.
+     *
+     * @return True if the animator is currently paused, false otherwise.
+     *
+     * @see #pause()
+     * @see #resume()
+     */
+    public boolean isPaused() {
+        return mPaused;
+    }
+
+    /**
      * The amount of time, in milliseconds, to delay processing the animation
      * after {@link #start()} is called.
      *
@@ -180,15 +251,58 @@
     }
 
     /**
+     * Adds a pause listener to this animator.
+     *
+     * @param listener the listener to be added to the current set of pause listeners
+     * for this animation.
+     */
+    public void addPauseListener(AnimatorPauseListener listener) {
+        if (mPauseListeners == null) {
+            mPauseListeners = new ArrayList<AnimatorPauseListener>();
+        }
+        mPauseListeners.add(listener);
+    }
+
+    /**
+     * Removes a pause listener from the set listening to this animation.
+     *
+     * @param listener the listener to be removed from the current set of pause
+     * listeners for this animation.
+     */
+    public void removePauseListener(AnimatorPauseListener listener) {
+        if (mPauseListeners == null) {
+            return;
+        }
+        mPauseListeners.remove(listener);
+        if (mPauseListeners.size() == 0) {
+            mPauseListeners = null;
+        }
+    }
+
+    /**
+     * Gets the set of {@link AnimatorPauseListener} objects that are currently
+     * listening for pause/resume events on this animator.
+     *
+     * @return ArrayList<AnimatorListener> The set of pause listeners.
+     */
+    public ArrayList<AnimatorPauseListener> getPauseListeners() {
+        return mPauseListeners;
+    }
+
+    /**
      * Removes all listeners from this object. This is equivalent to calling
-     * <code>getListeners()</code> followed by calling <code>clear()</code> on the
-     * returned list of listeners.
+     * {@link #getListeners()} and {@link #getPauseListeners()} followed by calling
+     * {@link ArrayList#clear()} on the returned lists of listeners.
      */
     public void removeAllListeners() {
         if (mListeners != null) {
             mListeners.clear();
             mListeners = null;
         }
+        if (mPauseListeners != null) {
+            mPauseListeners.clear();
+            mPauseListeners = null;
+        }
     }
 
     @Override
@@ -203,6 +317,14 @@
                     anim.mListeners.add(oldListeners.get(i));
                 }
             }
+            if (mPauseListeners != null) {
+                ArrayList<AnimatorPauseListener> oldListeners = mPauseListeners;
+                anim.mPauseListeners = new ArrayList<AnimatorPauseListener>();
+                int numListeners = oldListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    anim.mPauseListeners.add(oldListeners.get(i));
+                }
+            }
             return anim;
         } catch (CloneNotSupportedException e) {
            throw new AssertionError();
@@ -280,4 +402,29 @@
          */
         void onAnimationRepeat(Animator animation);
     }
+
+    /**
+     * A pause listener receives notifications from an animation when the
+     * animation is {@link #pause() paused} or {@link #resume() resumed}.
+     *
+     * @see #addPauseListener(AnimatorPauseListener)
+     */
+    public static interface AnimatorPauseListener {
+        /**
+         * <p>Notifies that the animation was paused.</p>
+         *
+         * @param animation The animaton being paused.
+         * @see #pause()
+         */
+        void onAnimationPause(Animator animation);
+
+        /**
+         * <p>Notifies that the animation was resumed, after being
+         * previously paused.</p>
+         *
+         * @param animation The animation being resumed.
+         * @see #resume()
+         */
+        void onAnimationResume(Animator animation);
+    }
 }
diff --git a/core/java/android/animation/AnimatorListenerAdapter.java b/core/java/android/animation/AnimatorListenerAdapter.java
index e5d70a4..2ecb8c3 100644
--- a/core/java/android/animation/AnimatorListenerAdapter.java
+++ b/core/java/android/animation/AnimatorListenerAdapter.java
@@ -21,7 +21,8 @@
  * Any custom listener that cares only about a subset of the methods of this listener can
  * simply subclass this adapter class instead of implementing the interface directly.
  */
-public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener {
+public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener,
+        Animator.AnimatorPauseListener {
 
     /**
      * {@inheritDoc}
@@ -51,4 +52,17 @@
     public void onAnimationStart(Animator animation) {
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationPause(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationResume(Animator animation) {
+    }
 }
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index b48853b..018a2d6 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -455,6 +455,36 @@
         }
     }
 
+    @Override
+    public void pause() {
+        boolean previouslyPaused = mPaused;
+        super.pause();
+        if (!previouslyPaused && mPaused) {
+            if (mDelayAnim != null) {
+                mDelayAnim.pause();
+            } else {
+                for (Node node : mNodes) {
+                    node.animation.pause();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void resume() {
+        boolean previouslyPaused = mPaused;
+        super.resume();
+        if (previouslyPaused && !mPaused) {
+            if (mDelayAnim != null) {
+                mDelayAnim.resume();
+            } else {
+                for (Node node : mNodes) {
+                    node.animation.resume();
+                }
+            }
+        }
+    }
+
     /**
      * {@inheritDoc}
      *
@@ -467,6 +497,7 @@
     public void start() {
         mTerminated = false;
         mStarted = true;
+        mPaused = false;
 
         if (mDuration >= 0) {
             // If the duration was set on this AnimatorSet, pass it along to all child animations
@@ -549,6 +580,7 @@
                             mPlayingSet.add(node.animation);
                         }
                     }
+                    mDelayAnim = null;
                 }
             });
             mDelayAnim.start();
@@ -787,6 +819,7 @@
                         }
                     }
                     mAnimatorSet.mStarted = false;
+                    mAnimatorSet.mPaused = false;
                 }
             }
         }
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index e370e4a..6394299 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -80,6 +80,20 @@
      */
     long mSeekTime = -1;
 
+    /**
+     * Set on the next frame after pause() is called, used to calculate a new startTime
+     * or delayStartTime which allows the animator to continue from the point at which
+     * it was paused. If negative, has not yet been set.
+     */
+    private long mPauseTime;
+
+    /**
+     * Set when an animator is resumed. This triggers logic in the next frame which
+     * actually resumes the animator.
+     */
+    private boolean mResumed = false;
+
+
     // The static sAnimationHandler processes the internal timing loop on which all animations
     // are based
     /**
@@ -147,7 +161,7 @@
     private boolean mStarted = false;
 
     /**
-     * Tracks whether we've notified listeners of the onAnimationSTart() event. This can be
+     * Tracks whether we've notified listeners of the onAnimationStart() event. This can be
      * complex to keep track of since we notify listeners at different times depending on
      * startDelay and whether start() was called before end().
      */
@@ -914,6 +928,7 @@
         mPlayingState = STOPPED;
         mStarted = true;
         mStartedDelay = false;
+        mPaused = false;
         AnimationHandler animationHandler = getOrCreateAnimationHandler();
         animationHandler.mPendingAnimations.add(this);
         if (mStartDelay == 0) {
@@ -971,6 +986,24 @@
     }
 
     @Override
+    public void resume() {
+        if (mPaused) {
+            mResumed = true;
+        }
+        super.resume();
+    }
+
+    @Override
+    public void pause() {
+        boolean previouslyPaused = mPaused;
+        super.pause();
+        if (!previouslyPaused && mPaused) {
+            mPauseTime = -1;
+            mResumed = false;
+        }
+    }
+
+    @Override
     public boolean isRunning() {
         return (mPlayingState == RUNNING || mRunning);
     }
@@ -1008,6 +1041,7 @@
         handler.mPendingAnimations.remove(this);
         handler.mDelayedAnims.remove(this);
         mPlayingState = STOPPED;
+        mPaused = false;
         if ((mStarted || mRunning) && mListeners != null) {
             if (!mRunning) {
                 // If it's not yet running, then start listeners weren't called. Call them now.
@@ -1071,6 +1105,18 @@
             mStartedDelay = true;
             mDelayStartTime = currentTime;
         } else {
+            if (mPaused) {
+                if (mPauseTime < 0) {
+                    mPauseTime = currentTime;
+                }
+                return false;
+            } else if (mResumed) {
+                mResumed = false;
+                if (mPauseTime > 0) {
+                    // Offset by the duration that the animation was paused
+                    mDelayStartTime += (currentTime - mPauseTime);
+                }
+            }
             long deltaTime = currentTime - mDelayStartTime;
             if (deltaTime > mStartDelay) {
                 // startDelay ended - start the anim and record the
@@ -1093,7 +1139,7 @@
      *
      * @param currentTime The current time, as tracked by the static timing handler
      * @return true if the animation's duration, including any repetitions due to
-     * <code>repeatCount</code> has been exceeded and the animation should be ended.
+     * <code>repeatCount</code>, has been exceeded and the animation should be ended.
      */
     boolean animationFrame(long currentTime) {
         boolean done = false;
@@ -1148,6 +1194,18 @@
                 mSeekTime = -1;
             }
         }
+        if (mPaused) {
+            if (mPauseTime < 0) {
+                mPauseTime = frameTime;
+            }
+            return false;
+        } else if (mResumed) {
+            mResumed = false;
+            if (mPauseTime > 0) {
+                // Offset by the duration that the animation was paused
+                mStartTime += (frameTime - mPauseTime);
+            }
+        }
         // The frame time might be before the start time during the first frame of
         // an animation.  The "current time" must always be on or after the start
         // time to avoid animating frames at negative time intervals.  In practice, this
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 1ea13e1..e062fa8 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1704,7 +1704,7 @@
         public void onGetDescriptor(String address, int srvcType,
                                     int srvcInstId, ParcelUuid srvcUuid,
                                     int charInstId, ParcelUuid charUuid,
-                                    ParcelUuid descUuid) {
+                                    int descInstId, ParcelUuid descUuid) {
             // no op
         }
 
@@ -1734,14 +1734,14 @@
         public void onDescriptorRead(String address, int status, int srvcType,
                                      int srvcInstId, ParcelUuid srvcUuid,
                                      int charInstId, ParcelUuid charUuid,
-                                     ParcelUuid descrUuid, byte[] value) {
+                                     int descInstId, ParcelUuid descrUuid, byte[] value) {
             // no op
         }
 
         public void onDescriptorWrite(String address, int status, int srvcType,
                                       int srvcInstId, ParcelUuid srvcUuid,
                                       int charInstId, ParcelUuid charUuid,
-                                      ParcelUuid descrUuid) {
+                                      int descInstId, ParcelUuid descrUuid) {
             // no op
         }
 
@@ -1752,6 +1752,10 @@
         public void onReadRemoteRssi(String address, int rssi, int status) {
             // no op
         }
+
+        public void onListen(int status) {
+            // no op
+        }
     }
 
 }
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index df3ec1a..a8c310b 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -261,7 +261,7 @@
             public void onGetDescriptor(String address, int srvcType,
                              int srvcInstId, ParcelUuid srvcUuid,
                              int charInstId, ParcelUuid charUuid,
-                             ParcelUuid descUuid) {
+                             int descrInstId, ParcelUuid descUuid) {
                 if (DBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid);
 
                 if (!address.equals(mDevice.getAddress())) {
@@ -276,7 +276,7 @@
                 if (characteristic == null) return;
 
                 characteristic.addDescriptor(new BluetoothGattDescriptor(
-                    characteristic, descUuid.getUuid(), 0));
+                    characteristic, descUuid.getUuid(), descrInstId, 0));
             }
 
             /**
@@ -429,7 +429,8 @@
             public void onDescriptorRead(String address, int status, int srvcType,
                              int srvcInstId, ParcelUuid srvcUuid,
                              int charInstId, ParcelUuid charUuid,
-                             ParcelUuid descrUuid, byte[] value) {
+                             int descrInstId, ParcelUuid descrUuid,
+                             byte[] value) {
                 if (DBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid);
 
                 if (!address.equals(mDevice.getAddress())) {
@@ -444,7 +445,7 @@
                 if (characteristic == null) return;
 
                 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
-                        descrUuid.getUuid());
+                        descrUuid.getUuid(), descrInstId);
                 if (descriptor == null) return;
 
                 if (status == 0) descriptor.setValue(value);
@@ -456,7 +457,7 @@
                         mAuthRetry = true;
                         mService.readDescriptor(mClientIf, address,
                             srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
-                            descrUuid, AUTHENTICATION_MITM);
+                            descrInstId, descrUuid, AUTHENTICATION_MITM);
                     } catch (RemoteException e) {
                         Log.e(TAG,"",e);
                     }
@@ -478,7 +479,7 @@
             public void onDescriptorWrite(String address, int status, int srvcType,
                              int srvcInstId, ParcelUuid srvcUuid,
                              int charInstId, ParcelUuid charUuid,
-                             ParcelUuid descrUuid) {
+                             int descrInstId, ParcelUuid descrUuid) {
                 if (DBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid);
 
                 if (!address.equals(mDevice.getAddress())) {
@@ -493,7 +494,7 @@
                 if (characteristic == null) return;
 
                 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
-                        descrUuid.getUuid());
+                        descrUuid.getUuid(), descrInstId);
                 if (descriptor == null) return;
 
                 if ((status == GATT_INSUFFICIENT_AUTHENTICATION
@@ -503,7 +504,7 @@
                         mAuthRetry = true;
                         mService.writeDescriptor(mClientIf, address,
                             srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
-                            descrUuid, characteristic.getWriteType(),
+                            descrInstId, descrUuid, characteristic.getWriteType(),
                             AUTHENTICATION_MITM, descriptor.getValue());
                     } catch (RemoteException e) {
                         Log.e(TAG,"",e);
@@ -552,6 +553,14 @@
                     Log.w(TAG, "Unhandled exception in callback", ex);
                 }
             }
+
+            /**
+             * Listen command status callback
+             * @hide
+             */
+            public void onListen(int status) {
+                if (DBG) Log.d(TAG, "onListen() - status=" + status);
+            }
         };
 
     /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device) {
@@ -684,6 +693,63 @@
         return true;
     }
 
+   /**
+     * Starts or stops sending of advertisement packages to listen for connection
+     * requests from a central devices.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param start Start or stop advertising
+     */
+    /*package*/ void listen(boolean start) {
+        if (DBG) Log.d(TAG, "listen() - start: " + start);
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.clientListen(mClientIf, start);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Sets the advertising data contained in the adv. response packet.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param advData true to set adv. data, false to set scan response
+     * @param includeName Inlucde the name in the adv. response
+     * @param includeTxPower Include TX power value
+     * @param minInterval Minimum desired scan interval (optional)
+     * @param maxInterval Maximum desired scan interval (optional)
+     * @param appearance The appearance flags for the device (optional)
+     * @param manufacturerData Manufacturer specific data including company ID (optional)
+     */
+    /*package*/ void setAdvData(boolean advData, boolean includeName, boolean includeTxPower,
+                           Integer minInterval, Integer maxInterval,
+                           Integer appearance, Byte[] manufacturerData) {
+        if (DBG) Log.d(TAG, "setAdvData()");
+        if (mService == null || mClientIf == 0) return;
+
+        byte[] data = new byte[0];
+        if (manufacturerData != null) {
+            data = new byte[manufacturerData.length];
+            for(int i = 0; i != manufacturerData.length; ++i) {
+                data[i] = manufacturerData[i];
+            }
+        }
+
+        try {
+            mService.setAdvData(mClientIf, !advData,
+                includeName, includeTxPower,
+                minInterval != null ? minInterval : 0,
+                maxInterval != null ? maxInterval : 0,
+                appearance != null ? appearance : 0, data);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
     /**
      * Disconnects an established connection, or cancels a connection attempt
      * currently in progress.
@@ -915,11 +981,11 @@
         if (device == null) return false;
 
         try {
-            mService.readDescriptor(mClientIf, device.getAddress(),
-                service.getType(), service.getInstanceId(),
-                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
-                new ParcelUuid(characteristic.getUuid()),
-                new ParcelUuid(descriptor.getUuid()), AUTHENTICATION_NONE);
+            mService.readDescriptor(mClientIf, device.getAddress(), service.getType(),
+                service.getInstanceId(), new ParcelUuid(service.getUuid()),
+                characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()),
+                descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()),
+                AUTHENTICATION_NONE);
         } catch (RemoteException e) {
             Log.e(TAG,"",e);
             return false;
@@ -953,11 +1019,10 @@
         if (device == null) return false;
 
         try {
-            mService.writeDescriptor(mClientIf, device.getAddress(),
-                service.getType(), service.getInstanceId(),
-                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
-                new ParcelUuid(characteristic.getUuid()),
-                new ParcelUuid(descriptor.getUuid()),
+            mService.writeDescriptor(mClientIf, device.getAddress(), service.getType(),
+                service.getInstanceId(), new ParcelUuid(service.getUuid()),
+                characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()),
+                descriptor.getInstanceId(), new ParcelUuid(descriptor.getUuid()),
                 characteristic.getWriteType(), AUTHENTICATION_NONE,
                 descriptor.getValue());
         } catch (RemoteException e) {
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 033f079..f0ecbb4 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -283,6 +283,20 @@
     }
 
     /**
+     * Get a descriptor by UUID and isntance id.
+     * @hide
+     */
+    /*package*/  BluetoothGattDescriptor getDescriptor(UUID uuid, int instanceId) {
+        for(BluetoothGattDescriptor descriptor : mDescriptors) {
+            if (descriptor.getUuid().equals(uuid)
+             && descriptor.getInstanceId() == instanceId) {
+                return descriptor;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the service this characteristic belongs to.
      * @return The asscociated service
      */
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
index 1cd6878..5f525dc 100644
--- a/core/java/android/bluetooth/BluetoothGattDescriptor.java
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -91,6 +91,12 @@
     protected UUID mUuid;
 
     /**
+     * Instance ID for this descriptor.
+     * @hide
+     */
+    protected int mInstance;
+
+    /**
      * Permissions for this descriptor
      * @hide
      */
@@ -116,7 +122,7 @@
      * @param permissions Permissions for this descriptor
      */
     public BluetoothGattDescriptor(UUID uuid, int permissions) {
-        initDescriptor(null, uuid, permissions);
+        initDescriptor(null, uuid, 0, permissions);
     }
 
     /**
@@ -128,14 +134,15 @@
      * @param permissions Permissions for this descriptor
      */
     /*package*/ BluetoothGattDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
-                                    int permissions) {
-        initDescriptor(characteristic, uuid, permissions);
+                                    int instance, int permissions) {
+        initDescriptor(characteristic, uuid, instance, permissions);
     }
 
     private void initDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
-                                int permissions) {
+                                int instance, int permissions) {
         mCharacteristic = characteristic;
         mUuid = uuid;
+        mInstance = instance;
         mPermissions = permissions;
     }
 
@@ -165,6 +172,21 @@
     }
 
     /**
+     * Returns the instance ID for this descriptor.
+     *
+     * <p>If a remote device offers multiple descriptors with the same UUID,
+     * the instance ID is used to distuinguish between descriptors.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Instance ID of this descriptor
+     * @hide
+     */
+    public int getInstanceId() {
+        return mInstance;
+    }
+
+    /**
      * Returns the permissions for this descriptor.
      *
      * @return Permissions of this descriptor
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
index 39a435b..1e66369 100644
--- a/core/java/android/bluetooth/BluetoothGattService.java
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -152,8 +152,8 @@
      */
     /*package*/ BluetoothGattCharacteristic getCharacteristic(UUID uuid, int instanceId) {
         for(BluetoothGattCharacteristic characteristic : mCharacteristics) {
-            if (uuid.equals(characteristic.getUuid()) &&
-                    mInstanceId == instanceId)
+            if (uuid.equals(characteristic.getUuid())
+             && characteristic.getInstanceId() == instanceId)
                 return characteristic;
         }
         return null;
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index c89d132..df393db 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -37,6 +37,10 @@
     void unregisterClient(in int clientIf);
     void clientConnect(in int clientIf, in String address, in boolean isDirect);
     void clientDisconnect(in int clientIf, in String address);
+    void clientListen(in int clientIf, in boolean start);
+    void setAdvData(in int clientIf, in boolean setScanRsp, in boolean inclName,
+                            in boolean inclTxPower, in int minInterval, in int maxInterval,
+                            in int appearance, in byte[] manufacturerData);
     void refreshDevice(in int clientIf, in String address);
     void discoverServices(in int clientIf, in String address);
     void readCharacteristic(in int clientIf, in String address, in int srvcType,
@@ -50,12 +54,13 @@
     void readDescriptor(in int clientIf, in String address, in int srvcType,
                             in int srvcInstanceId, in ParcelUuid srvcId,
                             in int charInstanceId, in ParcelUuid charId,
-                            in ParcelUuid descrUuid, in int authReq);
+                            in int descrInstanceId, in ParcelUuid descrUuid,
+                            in int authReq);
     void writeDescriptor(in int clientIf, in String address, in int srvcType,
                             in int srvcInstanceId, in ParcelUuid srvcId,
                             in int charInstanceId, in ParcelUuid charId,
-                            in ParcelUuid descrId, in int writeType,
-                            in int authReq, in byte[] value);
+                            in int descrInstanceId, in ParcelUuid descrId,
+                            in int writeType, in int authReq, in byte[] value);
     void registerForNotification(in int clientIf, in String address, in int srvcType,
                             in int srvcInstanceId, in ParcelUuid srvcId,
                             in int charInstanceId, in ParcelUuid charId,
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
index fc52172..60c297b 100644
--- a/core/java/android/bluetooth/IBluetoothGattCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -39,7 +39,7 @@
     void onGetDescriptor(in String address, in int srvcType,
                              in int srvcInstId, in ParcelUuid srvcUuid,
                              in int charInstId, in ParcelUuid charUuid,
-                             in ParcelUuid descrUuid);
+                             in int descrInstId, in ParcelUuid descrUuid);
     void onSearchComplete(in String address, in int status);
     void onCharacteristicRead(in String address, in int status, in int srvcType,
                              in int srvcInstId, in ParcelUuid srvcUuid,
@@ -52,14 +52,16 @@
     void onDescriptorRead(in String address, in int status, in int srvcType,
                              in int srvcInstId, in ParcelUuid srvcUuid,
                              in int charInstId, in ParcelUuid charUuid,
-                             in ParcelUuid descrUuid, in byte[] value);
+                             in int descrInstId, in ParcelUuid descrUuid,
+                             in byte[] value);
     void onDescriptorWrite(in String address, in int status, in int srvcType,
                              in int srvcInstId, in ParcelUuid srvcUuid,
                              in int charInstId, in ParcelUuid charUuid,
-                             in ParcelUuid descrUuid);
+                             in int descrInstId, in ParcelUuid descrUuid);
     void onNotify(in String address, in int srvcType,
                              in int srvcInstId, in ParcelUuid srvcUuid,
                              in int charInstId, in ParcelUuid charUuid,
                              in byte[] value);
     void onReadRemoteRssi(in String address, in int rssi, in int status);
+    void onListen(in int status);
 }
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 1c28138..0402eeb 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -786,9 +786,11 @@
             // 2 most significant bits in screenLayout).
             setLayoutDirection(locale);
         }
-        if (delta.screenLayout != 0 && screenLayout != delta.screenLayout) {
+        final int deltaScreenLayoutDir = delta.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
+        if (deltaScreenLayoutDir != SCREENLAYOUT_LAYOUTDIR_UNDEFINED &&
+                deltaScreenLayoutDir != (screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
+            screenLayout = (screenLayout & ~SCREENLAYOUT_LAYOUTDIR_MASK) | deltaScreenLayoutDir;
             changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
-            setLayoutDirection(locale);
         }
         if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
         {
@@ -937,7 +939,9 @@
             changed |= ActivityInfo.CONFIG_LOCALE;
             changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
         }
-        if (delta.screenLayout != 0 && screenLayout != delta.screenLayout) {
+        final int deltaScreenLayoutDir = delta.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
+        if (deltaScreenLayoutDir != SCREENLAYOUT_LAYOUTDIR_UNDEFINED &&
+                deltaScreenLayoutDir != (screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
             changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
         }
         if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 422d827..c3b532d 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -173,6 +173,11 @@
      * <p>This function can take several hundred milliseconds to execute, since
      * camera hardware may need to be powered on or reconfigured.</p>
      *
+     * <p>The camera device will query each Surface's size and formats upon this
+     * call, so they must be set to a valid setting at this time (in particular:
+     * if the format is user-visible, it must be one of android.scaler.availableFormats;
+     * and the size must be one of android.scaler.available[Processed|Jpeg]Sizes).</p>
+     *
      * <p>To change the configuration after requests have been submitted to the
      * device, the camera device must be idle. To idle the device, stop any
      * repeating requests with {@link #stopRepeating stopRepeating}, and then
diff --git a/core/java/android/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java
index e67d0d7..21de9f5 100644
--- a/core/java/android/hardware/location/GeofenceHardware.java
+++ b/core/java/android/hardware/location/GeofenceHardware.java
@@ -15,16 +15,11 @@
  */
 package android.hardware.location;
 
-import android.content.Context;
 import android.location.Location;
 import android.os.RemoteException;
-import android.util.Log;
 
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
 
 /**
  * This class handles geofences managed by various hardware subsystems. It contains
@@ -52,7 +47,7 @@
     private IGeofenceHardware mService;
 
     // Hardware systems that do geofence monitoring.
-    static final int NUM_MONITORS = 1;
+    static final int NUM_MONITORS = 2;
 
     /**
      * Constant for geofence monitoring done by the GPS hardware.
@@ -60,6 +55,13 @@
     public static final int MONITORING_TYPE_GPS_HARDWARE = 0;
 
     /**
+     * Constant for geofence monitoring done by the Fused hardware.
+     *
+     * @hide
+     */
+    public static final int MONITORING_TYPE_FUSED_HARDWARE = 1;
+
+    /**
      * Constant to indiciate that the monitoring system is currently
      * available for monitoring geofences.
      */
@@ -124,8 +126,12 @@
      */
     public static final int GEOFENCE_FAILURE = 5;
 
-    static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L;
-    static final int GPS_GEOFENCE_AVAILABLE = 1<<1L;
+    /**
+     * The constant used to indicate that the operation failed due to insufficient memory.
+     *
+     * @hide
+     */
+    public static final int GEOFENCE_ERROR_INSUFFICIENT_MEMORY = 6;
 
     private HashMap<GeofenceHardwareCallback, GeofenceHardwareCallbackWrapper>
             mCallbacks = new HashMap<GeofenceHardwareCallback, GeofenceHardwareCallbackWrapper>();
diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java
index 77e3143..eac6620 100644
--- a/core/java/android/hardware/location/GeofenceHardwareImpl.java
+++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java
@@ -18,23 +18,21 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.location.FusedBatchOptions;
+import android.location.IFusedGeofenceHardware;
 import android.location.IGpsGeofenceHardware;
 import android.location.Location;
 import android.location.LocationManager;
-import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 
 /**
  * This class manages the geofences which are handled by hardware.
@@ -54,6 +52,7 @@
             new ArrayList[GeofenceHardware.NUM_MONITORS];
     private final ArrayList<Reaper> mReapers = new ArrayList<Reaper>();
 
+    private IFusedGeofenceHardware mFusedService;
     private IGpsGeofenceHardware mGpsService;
 
     private int[] mSupportedMonitorTypes = new int[GeofenceHardware.NUM_MONITORS];
@@ -67,7 +66,7 @@
     private static final int GEOFENCE_CALLBACK_BINDER_DIED = 6;
 
     // mCallbacksHandler message types
-    private static final int GPS_GEOFENCE_STATUS = 1;
+    private static final int GEOFENCE_STATUS = 1;
     private static final int CALLBACK_ADD = 2;
     private static final int CALLBACK_REMOVE = 3;
     private static final int MONITOR_CALLBACK_BINDER_DIED = 4;
@@ -91,16 +90,6 @@
     private static final int RESOLUTION_LEVEL_COARSE = 2;
     private static final int RESOLUTION_LEVEL_FINE = 3;
 
-    // GPS Geofence errors. Should match gps.h constants.
-    private static final int GPS_GEOFENCE_OPERATION_SUCCESS = 0;
-    private static final int GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 100;
-    private static final int GPS_GEOFENCE_ERROR_ID_EXISTS  = -101;
-    private static final int GPS_GEOFENCE_ERROR_ID_UNKNOWN = -102;
-    private static final int GPS_GEOFENCE_ERROR_INVALID_TRANSITION = -103;
-    private static final int GPS_GEOFENCE_ERROR_GENERIC = -149;
-
-
-
     public synchronized static GeofenceHardwareImpl getInstance(Context context) {
         if (sInstance == null) {
             sInstance = new GeofenceHardwareImpl(context);
@@ -113,6 +102,9 @@
         // Init everything to unsupported.
         setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
                 GeofenceHardware.MONITOR_UNSUPPORTED);
+        setMonitorAvailability(
+                GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
+                GeofenceHardware.MONITOR_UNSUPPORTED);
 
     }
 
@@ -147,6 +139,22 @@
         }
     }
 
+    private void updateFusedHardwareAvailability() {
+        boolean fusedSupported;
+        try {
+            fusedSupported = mFusedService.isSupported();
+        } catch(RemoteException e) {
+            Log.e(TAG, "RemoteException calling LocationManagerService");
+            fusedSupported = false;
+        }
+
+        if(fusedSupported) {
+            setMonitorAvailability(
+                    GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
+                    GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE);
+        }
+    }
+
     public void setGpsHardwareGeofence(IGpsGeofenceHardware service) {
         if (mGpsService == null) {
             mGpsService = service;
@@ -159,12 +167,39 @@
         }
     }
 
+    public void setFusedGeofenceHardware(IFusedGeofenceHardware service) {
+        if(mFusedService == null) {
+            mFusedService = service;
+            updateFusedHardwareAvailability();
+        } else if(service == null) {
+            mFusedService = null;
+            Log.w(TAG, "Fused Geofence Hardware service seems to have crashed");
+        } else {
+            Log.e(TAG, "Error: FusedService being set again");
+        }
+    }
+
     public int[] getMonitoringTypes() {
+        boolean gpsSupported;
+        boolean fusedSupported;
         synchronized (mSupportedMonitorTypes) {
-            if (mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE] !=
-                        GeofenceHardware.MONITOR_UNSUPPORTED) {
-                return new int[] {GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE};
+            gpsSupported = mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE]
+                    != GeofenceHardware.MONITOR_UNSUPPORTED;
+            fusedSupported = mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE]
+                    != GeofenceHardware.MONITOR_UNSUPPORTED;
+        }
+
+        if(gpsSupported) {
+            if(fusedSupported) {
+                return new int[] {
+                        GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
+                        GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE };
+            } else {
+                return new int[] { GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE };
             }
+        } else if (fusedSupported) {
+            return new int[] { GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE };
+        } else {
             return new int[0];
         }
     }
@@ -213,6 +248,30 @@
                     result = false;
                 }
                 break;
+            case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
+                if(mFusedService == null) {
+                    return false;
+                }
+                GeofenceHardwareRequest request = GeofenceHardwareRequest.createCircularGeofence(
+                        latitude,
+                        longitude,
+                        radius);
+                request.setUnknownTimer(unknownTimer);
+                request.setNotificationResponsiveness(notificationResponsivenes);
+                request.setMonitorTransitions(monitorTransitions);
+                request.setLastTransition(lastTransition);
+
+                GeofenceHardwareRequestParcelable parcelableRequest =
+                        new GeofenceHardwareRequestParcelable(geofenceId, request);
+                try {
+                    mFusedService.addGeofences(
+                            new GeofenceHardwareRequestParcelable[] { parcelableRequest });
+                    result = true;
+                } catch(RemoteException e) {
+                    Log.e(TAG, "AddGeofence: RemoteException calling LocationManagerService");
+                    result = false;
+                }
+                break;
             default:
                 result = false;
         }
@@ -251,6 +310,18 @@
                     result = false;
                 }
                 break;
+            case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
+                if(mFusedService == null) {
+                    return false;
+                }
+                try {
+                    mFusedService.removeGeofences(new int[] { geofenceId });
+                    result = true;
+                } catch(RemoteException e) {
+                    Log.e(TAG, "RemoveGeofence: RemoteException calling LocationManagerService");
+                    result = false;
+                }
+                break;
             default:
                 result = false;
         }
@@ -278,6 +349,18 @@
                     result = false;
                 }
                 break;
+            case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
+                if(mFusedService == null) {
+                    return false;
+                }
+                try {
+                    mFusedService.pauseMonitoringGeofence(geofenceId);
+                    result = true;
+                } catch(RemoteException e) {
+                    Log.e(TAG, "PauseGeofence: RemoteException calling LocationManagerService");
+                    result = false;
+                }
+                break;
             default:
                 result = false;
         }
@@ -306,6 +389,18 @@
                     result = false;
                 }
                 break;
+            case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
+                if(mFusedService == null) {
+                    return false;
+                }
+                try {
+                    mFusedService.resumeMonitoringGeofence(geofenceId, monitorTransition);
+                    result = true;
+                } catch(RemoteException e) {
+                    Log.e(TAG, "ResumeGeofence: RemoteException calling LocationManagerService");
+                    result = false;
+                }
+                break;
             default:
                 result = false;
         }
@@ -334,127 +429,106 @@
         return true;
     }
 
-    private Location getLocation(int flags, double latitude,
-            double longitude, double altitude, float speed, float bearing, float accuracy,
-            long timestamp) {
-        if (DEBUG) Log.d(TAG, "GetLocation: " + flags + ":" + latitude);
-        Location location = new Location(LocationManager.GPS_PROVIDER);
-        if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
-            location.setLatitude(latitude);
-            location.setLongitude(longitude);
-            location.setTime(timestamp);
-            // It would be nice to push the elapsed real-time timestamp
-            // further down the stack, but this is still useful
-            location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+    /**
+     * Used to report geofence transitions
+     */
+    public void reportGeofenceTransition(
+            int geofenceId,
+            Location location,
+            int transition,
+            long transitionTimestamp,
+            int monitoringType,
+            int sourcesUsed) {
+        if(location == null) {
+            Log.e(TAG, String.format("Invalid Geofence Transition: location=%p", location));
+            return;
         }
-        if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
-            location.setAltitude(altitude);
-        } else {
-            location.removeAltitude();
+        if(DEBUG) {
+            Log.d(
+                    TAG,
+                    "GeofenceTransition| " + location + ", transition:" + transition +
+                    ", transitionTimestamp:" + transitionTimestamp + ", monitoringType:" +
+                    monitoringType + ", sourcesUsed:" + sourcesUsed);
         }
-        if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
-            location.setSpeed(speed);
-        } else {
-            location.removeSpeed();
-        }
-        if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
-            location.setBearing(bearing);
-        } else {
-            location.removeBearing();
-        }
-        if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
-            location.setAccuracy(accuracy);
-        } else {
-            location.removeAccuracy();
-        }
-        return location;
+
+        GeofenceTransition geofenceTransition = new GeofenceTransition(
+                geofenceId,
+                transition,
+                transitionTimestamp,
+                location,
+                monitoringType,
+                sourcesUsed);
+        acquireWakeLock();
+
+        Message message = mGeofenceHandler.obtainMessage(
+                GEOFENCE_TRANSITION_CALLBACK,
+                geofenceTransition);
+        message.sendToTarget();
     }
 
     /**
-     * called from GpsLocationProvider to report geofence transition
+     * Used to report Monitor status changes.
      */
-    public void reportGpsGeofenceTransition(int geofenceId, int flags, double latitude,
-            double longitude, double altitude, float speed, float bearing, float accuracy,
-            long timestamp, int transition, long transitionTimestamp) {
-        if (DEBUG) Log.d(TAG, "GeofenceTransition: Flags: " + flags + " Lat: " + latitude +
-            " Long: " + longitude + " Altitude: " + altitude + " Speed: " + speed + " Bearing: " +
-            bearing + " Accuracy: " + accuracy + " Timestamp: " + timestamp + " Transition: " +
-            transition + " TransitionTimestamp: " + transitionTimestamp);
-        Location location = getLocation(flags, latitude, longitude, altitude, speed, bearing,
-                accuracy, timestamp);
-        GeofenceTransition t = new GeofenceTransition(geofenceId, transition, timestamp, location);
+    public void reportGeofenceMonitorStatus(
+            int monitoringType,
+            int monitoringStatus,
+            Location location,
+            int source) {
+        // TODO: use the source if needed in the future
+        setMonitorAvailability(monitoringType, monitoringStatus);
         acquireWakeLock();
-        Message m = mGeofenceHandler.obtainMessage(GEOFENCE_TRANSITION_CALLBACK, t);
-        mGeofenceHandler.sendMessage(m);
+        Message message = mCallbacksHandler.obtainMessage(GEOFENCE_STATUS, location);
+        message.arg1 = monitoringStatus;
+        message.arg2 = monitoringType;
+        message.sendToTarget();
     }
 
     /**
-     * called from GpsLocationProvider to report GPS status change.
+     * Internal generic status report function for Geofence operations.
+     *
+     * @param operation The operation to be reported as defined internally.
+     * @param geofenceId The id of the geofence the operation is related to.
+     * @param operationStatus The status of the operation as defined in GeofenceHardware class. This
+     *                        status is independent of the statuses reported by different HALs.
      */
-    public void reportGpsGeofenceStatus(int status, int flags, double latitude,
-            double longitude, double altitude, float speed, float bearing, float accuracy,
-            long timestamp) {
-        Location location = getLocation(flags, latitude, longitude, altitude, speed, bearing,
-                accuracy, timestamp);
-        boolean available = false;
-        if (status == GeofenceHardware.GPS_GEOFENCE_AVAILABLE) available = true;
-
-        int val = (available ? GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE :
-                GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE);
-        setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, val);
-
+    private void reportGeofenceOperationStatus(int operation, int geofenceId, int operationStatus) {
         acquireWakeLock();
-        Message m = mCallbacksHandler.obtainMessage(GPS_GEOFENCE_STATUS, location);
-        m.arg1 = val;
-        mCallbacksHandler.sendMessage(m);
+        Message message = mGeofenceHandler.obtainMessage(operation);
+        message.arg1 = geofenceId;
+        message.arg2 = operationStatus;
+        message.sendToTarget();
     }
 
     /**
-     * called from GpsLocationProvider add geofence callback.
+     * Used to report the status of a Geofence Add operation.
      */
-    public void reportGpsGeofenceAddStatus(int geofenceId, int status) {
-        if (DEBUG) Log.d(TAG, "Add Callback: GPS : Id: " + geofenceId + " Status: " + status);
-        acquireWakeLock();
-        Message m = mGeofenceHandler.obtainMessage(ADD_GEOFENCE_CALLBACK);
-        m.arg1 = geofenceId;
-        m.arg2 = getGeofenceStatus(status);
-        mGeofenceHandler.sendMessage(m);
+    public void reportGeofenceAddStatus(int geofenceId, int status) {
+        if(DEBUG) Log.d(TAG, "AddCallback| id:" + geofenceId + ", status:" + status);
+        reportGeofenceOperationStatus(ADD_GEOFENCE_CALLBACK, geofenceId, status);
     }
 
     /**
-     * called from GpsLocationProvider remove geofence callback.
+     * Used to report the status of a Geofence Remove operation.
      */
-    public void reportGpsGeofenceRemoveStatus(int geofenceId, int status) {
-        if (DEBUG) Log.d(TAG, "Remove Callback: GPS : Id: " + geofenceId + " Status: " + status);
-        acquireWakeLock();
-        Message m = mGeofenceHandler.obtainMessage(REMOVE_GEOFENCE_CALLBACK);
-        m.arg1 = geofenceId;
-        m.arg2 = getGeofenceStatus(status);
-        mGeofenceHandler.sendMessage(m);
+    public void reportGeofenceRemoveStatus(int geofenceId, int status) {
+        if(DEBUG) Log.d(TAG, "RemoveCallback| id:" + geofenceId + ", status:" + status);
+        reportGeofenceOperationStatus(REMOVE_GEOFENCE_CALLBACK, geofenceId, status);
     }
 
     /**
-     * called from GpsLocationProvider pause geofence callback.
+     * Used to report the status of a Geofence Pause operation.
      */
-    public void reportGpsGeofencePauseStatus(int geofenceId, int status) {
-        if (DEBUG) Log.d(TAG, "Pause Callback: GPS : Id: " + geofenceId + " Status: " + status);
-        acquireWakeLock();
-        Message m = mGeofenceHandler.obtainMessage(PAUSE_GEOFENCE_CALLBACK);
-        m.arg1 = geofenceId;
-        m.arg2 = getGeofenceStatus(status);
-        mGeofenceHandler.sendMessage(m);
+    public void reportGeofencePauseStatus(int geofenceId, int status) {
+        if(DEBUG) Log.d(TAG, "PauseCallbac| id:" + geofenceId + ", status" + status);
+        reportGeofenceOperationStatus(PAUSE_GEOFENCE_CALLBACK, geofenceId, status);
     }
 
     /**
-     * called from GpsLocationProvider resume geofence callback.
+     * Used to report the status of a Geofence Resume operation.
      */
-    public void reportGpsGeofenceResumeStatus(int geofenceId, int status) {
-        if (DEBUG) Log.d(TAG, "Resume Callback: GPS : Id: " + geofenceId + " Status: " + status);
-        acquireWakeLock();
-        Message m = mGeofenceHandler.obtainMessage(RESUME_GEOFENCE_CALLBACK);
-        m.arg1 = geofenceId;
-        m.arg2 = getGeofenceStatus(status);
-        mGeofenceHandler.sendMessage(m);
+    public void reportGeofenceResumeStatus(int geofenceId, int status) {
+        if(DEBUG) Log.d(TAG, "ResumeCallback| id:" + geofenceId + ", status:" + status);
+        reportGeofenceOperationStatus(RESUME_GEOFENCE_CALLBACK, geofenceId, status);
     }
 
     // All operations on mGeofences
@@ -539,7 +613,7 @@
                             callback.onGeofenceTransition(
                                     geofenceTransition.mGeofenceId, geofenceTransition.mTransition,
                                     geofenceTransition.mLocation, geofenceTransition.mTimestamp,
-                                    GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE);
+                                    geofenceTransition.mMonitoringType);
                         } catch (RemoteException e) {}
                     }
                     releaseWakeLock();
@@ -571,21 +645,20 @@
             IGeofenceHardwareMonitorCallback callback;
 
             switch (msg.what) {
-                case GPS_GEOFENCE_STATUS:
+                case GEOFENCE_STATUS:
                     Location location = (Location) msg.obj;
                     int val = msg.arg1;
+                    monitoringType = msg.arg2;
                     boolean available;
                     available = (val == GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE ?
                             true : false);
-                    callbackList = mCallbacks[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE];
+                    callbackList = mCallbacks[monitoringType];
                     if (callbackList != null) {
                         if (DEBUG) Log.d(TAG, "MonitoringSystemChangeCallback: GPS : " + available);
 
                         for (IGeofenceHardwareMonitorCallback c: callbackList) {
                             try {
-                                c.onMonitoringSystemChange(
-                                        GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, available,
-                                        location);
+                                c.onMonitoringSystemChange(monitoringType, available, location);
                             } catch (RemoteException e) {}
                         }
                     }
@@ -666,12 +739,22 @@
         private int mGeofenceId, mTransition;
         private long mTimestamp;
         private Location mLocation;
+        private int mMonitoringType;
+        private int mSourcesUsed;
 
-        GeofenceTransition(int geofenceId, int transition, long timestamp, Location location) {
+        GeofenceTransition(
+                int geofenceId,
+                int transition,
+                long timestamp,
+                Location location,
+                int monitoringType,
+                int sourcesUsed) {
             mGeofenceId = geofenceId;
             mTransition = transition;
             mTimestamp = timestamp;
             mLocation = location;
+            mMonitoringType = monitoringType;
+            mSourcesUsed = sourcesUsed;
         }
     }
 
@@ -686,6 +769,8 @@
         switch (monitoringType) {
             case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
                 return RESOLUTION_LEVEL_FINE;
+            case GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE:
+                return RESOLUTION_LEVEL_FINE;
         }
         return RESOLUTION_LEVEL_NONE;
     }
@@ -752,22 +837,4 @@
             return RESOLUTION_LEVEL_NONE;
         }
     }
-
-    private int getGeofenceStatus(int status) {
-        switch (status) {
-            case GPS_GEOFENCE_OPERATION_SUCCESS:
-                return GeofenceHardware.GEOFENCE_SUCCESS;
-            case GPS_GEOFENCE_ERROR_GENERIC:
-                return GeofenceHardware.GEOFENCE_FAILURE;
-            case GPS_GEOFENCE_ERROR_ID_EXISTS:
-                return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS;
-            case GPS_GEOFENCE_ERROR_INVALID_TRANSITION:
-                return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION;
-            case GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES:
-                return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
-            case GPS_GEOFENCE_ERROR_ID_UNKNOWN:
-                return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN;
-        }
-        return -1;
-    }
 }
diff --git a/packages/SystemUI/res/values-land/refs.xml b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.aidl
similarity index 67%
copy from packages/SystemUI/res/values-land/refs.xml
copy to core/java/android/hardware/location/GeofenceHardwareRequestParcelable.aidl
index 62fb77d..b599d44 100644
--- a/packages/SystemUI/res/values-land/refs.xml
+++ b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.aidl
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (c) 2013, The Android Open Source Project
+/*
+ * Copyright (C) 2013, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,8 +12,8 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
-*/
--->
-<resources>
-    <item type="string" name="hiding_navigation_confirmation_message">@string/hiding_navigation_confirmation_message_long</item>
-</resources>
+ */
+
+package android.hardware.location;
+
+parcelable GeofenceHardwareRequestParcelable;
diff --git a/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java
new file mode 100644
index 0000000..40e7fc4
--- /dev/null
+++ b/core/java/android/hardware/location/GeofenceHardwareRequestParcelable.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Geofence Hardware Request used for internal location services communication.
+ *
+ * @hide
+ */
+public final class GeofenceHardwareRequestParcelable implements Parcelable {
+    private GeofenceHardwareRequest mRequest;
+    private int mId;
+
+    public GeofenceHardwareRequestParcelable(int id, GeofenceHardwareRequest request) {
+        mId = id;
+        mRequest = request;
+    }
+
+    /**
+     * Returns the id of this request.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the latitude of this geofence.
+     */
+    public double getLatitude() {
+        return mRequest.getLatitude();
+    }
+
+    /**
+     * Returns the longitude of this geofence.
+     */
+    public double getLongitude() {
+        return mRequest.getLongitude();
+    }
+
+    /**
+     * Returns the radius of this geofence.
+     */
+    public double getRadius() {
+        return mRequest.getRadius();
+    }
+
+    /**
+     * Returns transitions monitored for this geofence.
+     */
+    public int getMonitorTransitions() {
+        return mRequest.getMonitorTransitions();
+    }
+
+    /**
+     * Returns the unknownTimer of this geofence.
+     */
+    public int getUnknownTimer() {
+        return mRequest.getUnknownTimer();
+    }
+
+    /**
+     * Returns the notification responsiveness of this geofence.
+     */
+    public int getNotificationResponsiveness() {
+        return mRequest.getNotificationResponsiveness();
+    }
+
+    /**
+     * Returns the last transition of this geofence.
+     */
+    public int getLastTransition() {
+        return mRequest.getLastTransition();
+    }
+
+    /**
+     * Returns the type of the geofence for the current request.
+     */
+    int getType() {
+        return mRequest.getType();
+    }
+
+    /**
+     * Method definitions to support Parcelable operations.
+     */
+    public static final Parcelable.Creator<GeofenceHardwareRequestParcelable> CREATOR = 
+            new Parcelable.Creator<GeofenceHardwareRequestParcelable>() {
+        @Override
+        public GeofenceHardwareRequestParcelable createFromParcel(Parcel parcel) {
+            int geofenceType = parcel.readInt();
+            if(geofenceType != GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) {
+                Log.e(
+                        "GeofenceHardwareRequest",
+                        String.format("Invalid Geofence type: %d", geofenceType));
+                return null;
+            }
+
+            GeofenceHardwareRequest request = GeofenceHardwareRequest.createCircularGeofence(
+                    parcel.readDouble(),
+                    parcel.readDouble(),
+                    parcel.readDouble());
+            request.setLastTransition(parcel.readInt());
+            request.setMonitorTransitions(parcel.readInt());
+            request.setUnknownTimer(parcel.readInt());
+            request.setNotificationResponsiveness(parcel.readInt());
+            
+            int id = parcel.readInt();
+            return new GeofenceHardwareRequestParcelable(id, request);
+        }
+
+        @Override
+        public GeofenceHardwareRequestParcelable[] newArray(int size) {
+            return new GeofenceHardwareRequestParcelable[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(getType());
+        parcel.writeDouble(getLatitude());
+        parcel.writeDouble(getLongitude());
+        parcel.writeDouble(getRadius());
+        parcel.writeInt(getLastTransition());
+        parcel.writeInt(getMonitorTransitions());
+        parcel.writeInt(getUnknownTimer());
+        parcel.writeInt(getNotificationResponsiveness());
+        parcel.writeInt(getId());
+    }
+}
diff --git a/core/java/android/hardware/location/GeofenceHardwareService.java b/core/java/android/hardware/location/GeofenceHardwareService.java
index 3bc70ee..fb238bd 100644
--- a/core/java/android/hardware/location/GeofenceHardwareService.java
+++ b/core/java/android/hardware/location/GeofenceHardwareService.java
@@ -20,6 +20,7 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.location.IFusedGeofenceHardware;
 import android.location.IGpsGeofenceHardware;
 import android.os.Binder;
 import android.os.IBinder;
@@ -68,6 +69,10 @@
             mGeofenceHardwareImpl.setGpsHardwareGeofence(service);
         }
 
+        public void setFusedGeofenceHardware(IFusedGeofenceHardware service) {
+            mGeofenceHardwareImpl.setFusedGeofenceHardware(service);
+        }
+
         public int[] getMonitoringTypes() {
             mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
                     "Location Hardware permission not granted to access hardware geofence");
diff --git a/core/java/android/hardware/location/IGeofenceHardware.aidl b/core/java/android/hardware/location/IGeofenceHardware.aidl
index 6900070..8900166 100644
--- a/core/java/android/hardware/location/IGeofenceHardware.aidl
+++ b/core/java/android/hardware/location/IGeofenceHardware.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.location;
 
+import android.location.IFusedGeofenceHardware;
 import android.location.IGpsGeofenceHardware;
 import android.hardware.location.IGeofenceHardwareCallback;
 import android.hardware.location.IGeofenceHardwareMonitorCallback;
@@ -23,6 +24,7 @@
 /** @hide */
 interface IGeofenceHardware {
     void setGpsGeofenceHardware(in IGpsGeofenceHardware service);
+    void setFusedGeofenceHardware(in IFusedGeofenceHardware service);
     int[] getMonitoringTypes();
     int getStatusOfMonitoringType(int monitoringType);
     boolean addCircularFence(int id,  int monitoringType, double lat, double longitude,
diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java
index 4503eda..dbc8b6f 100644
--- a/core/java/android/print/PrintFileDocumentAdapter.java
+++ b/core/java/android/print/PrintFileDocumentAdapter.java
@@ -41,7 +41,7 @@
  * spooling the data, so you can deleted the file if it is a
  * temporary one.
  */
-public final class PrintFileDocumentAdapter extends PrintDocumentAdapter {
+public class PrintFileDocumentAdapter extends PrintDocumentAdapter {
 
     private static final String LOG_TAG = "PrintedFileDocumentAdapter";
 
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
index 5bc0f2e..92dc0dd 100644
--- a/core/java/android/printservice/PrinterDiscoverySession.java
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -109,21 +109,19 @@
      * Removed printers can be added again. You can call this method multiple
      * times during printer discovery.
      * <p>
-     * <strong>Note: </strong> Calling this method when the session is closed,
-     * which is if {@link #isClosed()} returns true, will throw an {@link
-     * IllegalStateException}.
+     * <strong>Note: </strong> Calls to this method before the session is opened,
+     * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
+     * i.e. after the call to {@link #onClose()}, will be ignored.
      * </p>
      *
      * @param printers The printers to add.
      *
      * @see #removePrinters(List)
      * @see #updatePrinters(List)
-     * @see #isClosed()
      */
     public final void addPrinters(List<PrinterInfo> printers) {
         final IPrinterDiscoverySessionObserver observer;
         synchronized (mLock) {
-            throwIfClosedLocked();
             observer = mObserver;
         }
         if (observer != null) {
@@ -132,6 +130,8 @@
             } catch (RemoteException re) {
                 Log.e(LOG_TAG, "Error adding printers", re);
             }
+        } else {
+            Log.w(LOG_TAG, "Printer discovery session not open not adding printers.");
         }
     }
 
@@ -140,21 +140,19 @@
      * printer has no effect. Removed printers can be added again. You
      * can call this method multiple times during printer discovery.
      * <p>
-     * <strong>Note: </strong> Calling this method when the session is closed,
-     * which is if {@link #isClosed()} returns true, will throw an {@link
-     * IllegalStateException}.
+     * <strong>Note: </strong> Calls to this method before the session is opened,
+     * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
+     * i.e. after the call to {@link #onClose()}, will be ignored.
      * </p>
      *
      * @param printerIds The ids of the removed printers.
      *
      * @see #addPrinters(List)
      * @see #updatePrinters(List)
-     * @see #isClosed()
      */
     public final void removePrinters(List<PrinterId> printerIds) {
         final IPrinterDiscoverySessionObserver observer;
         synchronized (mLock) {
-            throwIfClosedLocked();
             observer = mObserver;
         }
         if (observer != null) {
@@ -163,6 +161,8 @@
             } catch (RemoteException re) {
                 Log.e(LOG_TAG, "Error removing printers", re);
             }
+        } else {
+            Log.w(LOG_TAG, "Printer discovery session not open not removing printers.");
         }
     }
 
@@ -171,21 +171,19 @@
      * was removed has no effect. You can call this method multiple times
      * during printer discovery.
      * <p>
-     * <strong>Note: </strong> Calling this method when the session is closed,
-     * which is if {@link #isClosed()} returns true, will throw an {@link
-     * IllegalStateException}.
+     * <strong>Note: </strong> Calls to this method before the session is opened,
+     * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
+     * i.e. after the call to {@link #onClose()}, will be ignored.
      * </p>
      *
      * @param printers The printers to update.
      *
      * @see #addPrinters(List)
      * @see #removePrinters(List)
-     * @see #isClosed()
      */
     public final void updatePrinters(List<PrinterInfo> printers) {
         final IPrinterDiscoverySessionObserver observer;
         synchronized (mLock) {
-            throwIfClosedLocked();
             observer = mObserver;
         }
         if (observer != null) {
@@ -194,6 +192,8 @@
             } catch (RemoteException re) {
                 Log.e(LOG_TAG, "Error updating printers", re);
             }
+        } else {
+            Log.w(LOG_TAG, "Printer discovery session not open not updating printers.");
         }
     }
 
@@ -217,7 +217,6 @@
      * </p>
      *
      * @see #onClose()
-     * @see #isClosed()
      * @see #addPrinters(List)
      * @see #removePrinters(List)
      * @see #updatePrinters(List)
@@ -226,16 +225,9 @@
 
     /**
      * Callback notifying you that the session is closed and you should stop
-     * printer discovery. After the session is closed and any attempt to call
-     * any of its methods will throw an exception. Whether a session is closed
-     * can be checked by calling {@link #isClosed()}. Once the session is closed
+     * printer discovery. After the session is closed any call to the methods
+     * of this instance will be ignored. Once the session is closed
      * it will never be opened again.
-     *
-     * @see #onOpen(List)
-     * @see #isClosed()
-     * @see #addPrinters(List)
-     * @see #removePrinters(List)
-     * @see #updatePrinters(List)
      */
     public abstract void onClose();
 
@@ -263,31 +255,13 @@
      */
     public abstract void onRequestPrinterUpdate(PrinterId printerId);
 
-    /**
-     * Gets whether this session is closed.
-     *
-     * @return Whether the session is closed.
-     */
-    public final boolean isClosed() {
-        synchronized (mLock) {
-            return (mController == null && mObserver == null);
-        }
-    }
-
     void close() {
         synchronized (mLock) {
-            throwIfClosedLocked();
             mController = null;
             mObserver = null;
         }
     }
 
-    private void throwIfClosedLocked() {
-        if (isClosed()) {
-            throw new IllegalStateException("Session is closed");
-        }
-    }
-
     private final class SessionHandler extends Handler {
         public static final int MSG_OPEN = 1;
         public static final int MSG_CLOSE = 2;
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index debf4ee..f43e4ab3 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -46,6 +46,8 @@
     private final int mGeneration;
     private final int mControllerNumber;
     private final String mName;
+    private final int mVendorId;
+    private final int mProductId;
     private final String mDescriptor;
     private final boolean mIsExternal;
     private final int mSources;
@@ -343,13 +345,15 @@
     };
 
     // Called by native code.
-    private InputDevice(int id, int generation, int controllerNumber, String name,
-            String descriptor, boolean isExternal, int sources, int keyboardType,
+    private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
+            int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
             KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasButtonUnderPad) {
         mId = id;
         mGeneration = generation;
         mControllerNumber = controllerNumber;
         mName = name;
+        mVendorId = vendorId;
+        mProductId = productId;
         mDescriptor = descriptor;
         mIsExternal = isExternal;
         mSources = sources;
@@ -364,6 +368,8 @@
         mGeneration = in.readInt();
         mControllerNumber = in.readInt();
         mName = in.readString();
+        mVendorId = in.readInt();
+        mProductId = in.readInt();
         mDescriptor = in.readString();
         mIsExternal = in.readInt() != 0;
         mSources = in.readInt();
@@ -443,6 +449,33 @@
     }
 
     /**
+     * Gets the vendor id for the given device, if available.
+     * <p>
+     * A vendor id uniquely identifies the company who manufactured the device. A value of 0 will
+     * be assigned where a vendor id is not available.
+     * </p>
+     *
+     * @return The vendor id of a given device
+     */
+    public int getVendorId() {
+        return mVendorId;
+    }
+
+    /**
+     * Gets the product id for the given device, if available.
+     * <p>
+     * A product id uniquely identifies which product within the address space of a given vendor,
+     * identified by the device's vendor id. A value of 0 will be assigned where a product id is
+     * not available.
+     * </p>
+     *
+     * @return The product id of a given device
+     */
+    public int getProductId() {
+        return mProductId;
+    }
+
+    /**
      * Gets the input device descriptor, which is a stable identifier for an input device.
      * <p>
      * An input device descriptor uniquely identifies an input device.  Its value
@@ -757,6 +790,8 @@
         out.writeInt(mGeneration);
         out.writeInt(mControllerNumber);
         out.writeString(mName);
+        out.writeInt(mVendorId);
+        out.writeInt(mProductId);
         out.writeString(mDescriptor);
         out.writeInt(mIsExternal ? 1 : 0);
         out.writeInt(mSources);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7a82892..c0db23c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2379,7 +2379,7 @@
      * when hiding the status bar with {@link #SYSTEM_UI_FLAG_FULLSCREEN} and/or hiding the
      * navigation bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} instead of having the system
      * clear these flags upon interaction.  The system may compensate by temporarily overlaying
-     * transparent system bars while also delivering the event.
+     * semi-transparent system bars while also delivering the event.
      */
     public static final int SYSTEM_UI_FLAG_ALLOW_TRANSIENT = 0x00000800;
 
@@ -16540,7 +16540,9 @@
      * @param visibility  Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
      * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN},
      * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
-     * and {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_ALLOW_TRANSIENT},
+     * {@link #SYSTEM_UI_FLAG_TRANSPARENT_STATUS},
+     * and {@link #SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION}.
      */
     public void setSystemUiVisibility(int visibility) {
         if (visibility != mSystemUiVisibility) {
@@ -16552,11 +16554,13 @@
     }
 
     /**
-     * Returns the last {@link #setSystemUiVisibility(int) that this view has requested.
+     * Returns the last {@link #setSystemUiVisibility(int)} that this view has requested.
      * @return  Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE},
      * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN},
      * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION},
-     * and {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
+     * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}, {@link #SYSTEM_UI_FLAG_ALLOW_TRANSIENT},
+     * {@link #SYSTEM_UI_FLAG_TRANSPARENT_STATUS},
+     * and {@link #SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION}.
      */
     public int getSystemUiVisibility() {
         return mSystemUiVisibility;
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index a6e68d0..0224fbe 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -48,6 +48,7 @@
 
 import java.io.BufferedWriter;
 import java.io.File;
+import java.io.OutputStream;
 import java.util.Map;
 
 /**
@@ -1034,6 +1035,34 @@
     }
 
     /**
+     * Exports the contents of this Webview as PDF. Only supported for API levels
+     * {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE} and above.
+     *
+     * @param out            The stream to export the PDF contents to. Cannot be null.
+     * @param width          The page width. Should be larger than 0.
+     * @param height         The page height. Should be larger than 0.
+     * @param resultCallback A callback to be invoked when the PDF content is exported.
+     *                       A true indicates success, and a false failure.
+     *
+     * TODO: explain method parameters, margins, consider making the callback
+     * return more meaningful information, explain any threading concerns, HW
+     * draw limitations, and make it public.
+     * TODO: at the moment we are asking app to provide paper size information (width
+     * and height). This is likely not ideal (I think need margin info too).
+     * Another approach would be using PrintAttributes. This is to be clarified later.
+     *
+     * TODO: explain this webview will not draw during export (onDraw will clear to
+     * background color) so recommend taking it offscreen, or putting in a layer with an
+     * overlaid progress UI / spinner.
+     * @hide
+     */
+    public void exportToPdf(OutputStream out, int width, int height,
+            ValueCallback<Boolean> resultCallback) {
+        checkThread();
+        mProvider.exportToPdf(out, width, height, resultCallback);
+    }
+
+    /**
      * Gets the current scale of this WebView.
      *
      * @return the current scale
@@ -1852,6 +1881,11 @@
             return WebView.this.getHorizontalScrollbarHeight();
         }
 
+        public void super_onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+                int l, int t, int r, int b) {
+            WebView.super.onDrawVerticalScrollBar(canvas, scrollBar, l, t, r, b);
+        }
+
         // ---- Access to (non-public) fields ----
         /** Raw setter for the scroll X value, without invoking onScrollChanged handlers etc. */
         public void setScrollXRaw(int scrollX) {
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index b930276..228de5b 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -2659,7 +2659,7 @@
     @Override
     public void evaluateJavaScript(String script, ValueCallback<String> resultCallback) {
         // K-only API not implemented in WebViewClassic.
-        throw new IllegalStateException("This API not supported in Classic WebView.");
+        throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
     }
 
     /**
@@ -2892,6 +2892,17 @@
     }
 
     /**
+     * See {@link WebView#exportToPdf()}
+     */
+    @Override
+    public void exportToPdf(java.io.OutputStream out, int width, int height,
+            ValueCallback<Boolean> resultCallback) {
+        // K-only API not implemented in WebViewClassic.
+        throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
+
+    }
+
+    /**
      * See {@link WebView#getScale()}
      */
     @Override
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 8c5c4ce..17b4061 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -40,6 +40,7 @@
 
 import java.io.BufferedWriter;
 import java.io.File;
+import java.io.OutputStream;
 import java.util.Map;
 
 /**
@@ -146,6 +147,9 @@
 
     public Picture capturePicture();
 
+    public void exportToPdf(OutputStream out, int width, int height,
+            ValueCallback<Boolean> resultCallback);
+
     public float getScale();
 
     public void setInitialScale(int scaleInPercent);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 0ed846b..07198c75 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3485,7 +3485,8 @@
             mLastY = Integer.MIN_VALUE;
         }
 
-        if (performButtonActionOnTouchDown(ev) && (mTouchMode == TOUCH_MODE_DOWN)) {
+        if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
+                && performButtonActionOnTouchDown(ev)) {
             removeCallbacks(mPendingCheckForTap);
         }
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9c21f0d..a315546 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -359,9 +359,7 @@
                             mDrawableRight = mDrawableStart;
                             mDrawableSizeRight = mDrawableSizeStart;
                             mDrawableHeightRight = mDrawableHeightStart;
-                        }
 
-                        if (mOverride) {
                             mDrawableLeft = mDrawableEnd;
                             mDrawableSizeLeft = mDrawableSizeEnd;
                             mDrawableHeightLeft = mDrawableHeightEnd;
@@ -374,9 +372,7 @@
                             mDrawableLeft = mDrawableStart;
                             mDrawableSizeLeft = mDrawableSizeStart;
                             mDrawableHeightLeft = mDrawableHeightStart;
-                        }
 
-                        if (mOverride) {
                             mDrawableRight = mDrawableEnd;
                             mDrawableSizeRight = mDrawableSizeEnd;
                             mDrawableHeightRight = mDrawableHeightEnd;
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index aaa1adaa..4b71e36 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -95,6 +95,8 @@
         mTN = new TN();
         mTN.mY = context.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.toast_y_offset);
+        mTN.mGravity = context.getResources().getInteger(
+                com.android.internal.R.integer.config_toastDefaultGravity);
     }
     
     /**
@@ -382,12 +384,12 @@
         private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
         final Handler mHandler = new Handler();    
 
-        int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+        int mGravity;
         int mX, mY;
         float mHorizontalMargin;
         float mVerticalMargin;
 
-       
+
         View mView;
         View mNextView;
 
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 70a4daa..6d46cf9 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -610,6 +610,7 @@
     const jchar* glyphs = value->getGlyphs();
     size_t glyphsCount = value->getGlyphsCount();
     jfloat totalAdvance = value->getTotalAdvance();
+    x += xOffsetForTextAlign(paint, totalAdvance);
     const float* positions = value->getPos();
     int bytesCount = glyphsCount * sizeof(jchar);
     const SkRect& r = value->getBounds();
@@ -617,8 +618,7 @@
     bounds.translate(x, y);
 
     renderer->drawText((const char*) glyphs, bytesCount, glyphsCount,
-            x + xOffsetForTextAlign(paint, totalAdvance), y, positions,
-            paint, totalAdvance, bounds);
+            x, y, positions, paint, totalAdvance, bounds);
 }
 
 static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int count,
@@ -646,6 +646,7 @@
     const jchar* glyphs = value->getGlyphs();
     size_t glyphsCount = value->getGlyphsCount();
     jfloat totalAdvance = value->getTotalAdvance();
+    x += xOffsetForTextAlign(paint, totalAdvance);
     const float* positions = value->getPos();
     int bytesCount = glyphsCount * sizeof(jchar);
     const SkRect& r = value->getBounds();
@@ -653,8 +654,7 @@
     bounds.translate(x, y);
 
     renderer->drawText((const char*) glyphs, bytesCount, glyphsCount,
-            x + xOffsetForTextAlign(paint, totalAdvance), y, positions,
-            paint, totalAdvance, bounds);
+            x, y, positions, paint, totalAdvance, bounds);
 }
 
 static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject clazz,
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 5c8e010..bef0f84 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -53,11 +53,15 @@
         return NULL;
     }
 
+    const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
+
     ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz,
-            gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
-             deviceInfo.getControllerNumber(), nameObj.get(), descriptorObj.get(),
-            deviceInfo.isExternal(), deviceInfo.getSources(), deviceInfo.getKeyboardType(),
-            kcmObj.get(), deviceInfo.hasVibrator(), deviceInfo.hasButtonUnderPad()));
+                gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
+                deviceInfo.getControllerNumber(), nameObj.get(),
+                static_cast<int32_t>(ident.vendor), static_cast<int32_t>(ident.product),
+                descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(),
+                deviceInfo.getKeyboardType(), kcmObj.get(), deviceInfo.hasVibrator(),
+                deviceInfo.hasButtonUnderPad()));
 
     const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
     for (size_t i = 0; i < ranges.size(); i++) {
@@ -88,7 +92,7 @@
 
     GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
             "<init>",
-            "(IIILjava/lang/String;Ljava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZ)V");
+            "(IIILjava/lang/String;IILjava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZ)V");
 
     GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
             "addMotionRange", "(IIFFFFF)V");
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 848c585..a9d4b0a 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"verhoed program-oorskakelings"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Verhoed dat die gebruiker na \'n ander program oorskakel."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"kry huidige program-inligting"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Laat die houer toe om private inligting oor die huidige program en dienste op die voorgrond van die skerm te herwin."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"monitor en beheer alle programlaaiery"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Laat die program toe om te monitor en te beheer hoe die stelsel aktiwiteite laai. Kwaadwillige programme kan dalk die stelsel heeltemal in gevaar stel. Hierdie toestemming is net nodig vir ontwikkeling, en nooit vir normale gebruik nie."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"stuur uitsending met pakket verwyder"</string>
@@ -479,8 +478,8 @@
     <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"Laat die program oudio-uitset vasvang en herlei."</string>
     <string name="permlab_captureVideoOutput" msgid="2246828773589094023">"vang video-uitset vas"</string>
     <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"Laat die program video-uitset vasvang en herlei."</string>
-    <string name="permlab_captureSecureVideoOutput" msgid="7815398969303382016">"vang sekure video-uitset vas"</string>
-    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"Laat die program sekure video-uitset vasvang en herlei."</string>
+    <string name="permlab_captureSecureVideoOutput" msgid="7815398969303382016">"vang veilige video-uitset vas"</string>
+    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"Laat die program veilige video-uitset vasvang en herlei."</string>
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"verander jou klankinstellings"</string>
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Laat die program toe om globale klankinstellings soos volume en watter luidspreker vir uitvoer gebruik word, te verander."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"neem klank op"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index b2ecd52..8fc907a 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"የትግበራ መቀያየርን ተከላከል"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"ተጠቃሚው ከሌላ መተግበሪያ ከመቀየር ይከላከላል።"</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"የአሁኑ የመተግበሪያ መረጃ ያግኙ"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"ያዢው በማያ ገጹ ፊት ላይ ስላለው የአሁኑ መተግበሪያ የግል መረጃ እንዲያመጣ ያስችለዋል።"</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"ሁሉንም መተግበሪያ ማስነሻ አሳይ እና ተቆጣጠር"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"እንቅስቃሴዎችን ስርዓቱ እንዴት እንደሚያስጀምር ለመከታተል እና ለመቆጣጠር ለመተግበሪያው ይፈቅዳሉ፡፡ ተንኮል አዘል መተግበሪያዎች የስርዓቱን ክብረ ገመና ሙሉለሙሉ ሊያጋልጡ ይችላሉ፡፡ ይህ ፍቃድ የሚያስፈልገው ለግንባታ ብቻ ነው፤ ለመደበኛ አጠቃቀም ፈጽሞ አይደለም፡፡"</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"አካታች የተወገደለት ስርጭት ላክ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index bc22b80..03cc7b8 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"منع التبديل بين التطبيقات"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"لمنع المستخدم من التبديل إلى تطبيق آخر."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"الحصول على معلومات عن التطبيق الحالي"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"للسماح للمالك باسترداد معلومات خاصة عن الخدمات والتطبيق الحالي في مقدمة الشاشة."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"مراقبة بدء تشغيل جميع التطبيقات والتحكم فيها"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"للسماح للتطبيق بمراقبة كيفية بدء النظام للأنشطة والتحكم فيها. قد تُعرِّض التطبيقات الضارة النظام للضرر بشكل كامل. لن تكون هناك حاجة لهذا الإذن سوى للتطوير فقط، وليس للاستخدام العادي على الإطلاق."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"إرسال بث الحزمة الذي تمت إزالته"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"يتيح للمالك الربط بواجهة المستوى العلوي لخدمة تلقّي الإشعارات الصوتية. ولن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"استدعاء تطبيق التهيئة الذي يوفره مشغل شبكة الجوال"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"للسماح للمالك باستدعاء تطبيق التهيئة الذي يوفره مشغل شبكة الجوال. لن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"الاستماع إلى ملاحظات حول أحوال الشبكة"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"للسماح للتطبيق بالاستماع إلى ملاحظات حول أحوال الشبكة. لا حاجة إلى هذا مع التطبيقات العادية."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"تعيين قواعد كلمة المرور"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"يمكنك التحكم في الطول والأحرف المسموح بها في كلمات مرور إلغاء تأمين الشاشة."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"مراقبة محاولات إلغاء قفل الشاشة"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 21b4d95..38ea228 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"предотвратяване на превключването между приложения"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Не позволява на потребителя да превключва към друго приложение."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"извличане на информация за текущото приложение"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Разрешава на собственика да извлича частна информация за текущото приложение и услуги на преден план на екрана."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"наблюдение и контрол на стартирането на всички приложения"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Разрешава на приложението да наблюдава и контролира как системата стартира дейности. Злонамерените приложения могат изцяло да компрометират системата. Това разрешение е нужно само за програмиране, никога за нормална употреба."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"изпращане на излъчване при премахнат пакет"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 7379498..dae5009 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"impedir els canvis d\'aplicació"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Impedeix que l\'usuari canviï a una altra aplicació."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"obtenció d\'informació de l\'aplicació actual"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permet que el titular recuperi informació privada sobre l\'aplicació i els serveis actual al primer pla de la pantalla."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"supervisa i controla tots els inicis d\'aplicacions"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permet que l\'aplicació supervisi i controli com el sistema inicia activitats. Les aplicacions malicioses poden comprometre totalment el sistema. Aquest permís només és necessari per al desenvolupament, mai per a l\'ús normal."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"enviar difusió d\'eliminació de paquet"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 5a3bdf8..8e16e3a 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"zabránění přepínání aplikací"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Zabrání uživateli přepnout na jinou aplikaci."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"získat informace o aktuální aplikaci"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Umožňuje držiteli získat soukromé informace o aktuální aplikaci a službách na popředí obrazovky."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"sledování a řízení spouštění všech aplikací"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Umožňuje aplikaci sledovat a řídit spouštění činností systémem. Škodlivé aplikace mohou systém zcela ovládnout. Toto oprávnění je požadováno pouze pro účely vývoje, nikdy pro běžné použití."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"odeslání vysílání o odstranění balíčku"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 56022e6..9a053b8 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"undgå programskift"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Forhindrer brugeren i at skifte til en anden app."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"få aktuelle app-oplysninger"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Tillader, at brugeren henter private oplysninger om den aktuelle applikation og de aktuelle tjenester i forgrunden af skærmen."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"overvåge og kontrollere åbning af alle apps"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Tillader, at appen kan overvåge og kontrollere, hvordan systemet starter aktiviteter. Ondsindede apps kan fuldstændig kompromittere systemet. Denne tilladelse er kun nødvendig til udvikling, aldrig til normal brug."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"send udsendelse om fjernet pakke"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index c0006dd..7e4d274 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"App-Wechsel verhindern"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Hindert den Nutzer daran, zu einer anderen App zu wechseln"</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"Informationen zur aktuellen App abrufen"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Ermöglicht es dem Inhaber, private Informationen zur aktuellen App und zu aktuellen Diensten im Vordergrund des Bildschirms abzurufen"</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"Start von Apps überwachen und steuern"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Ermöglicht der App, den Start von Systemaktivitäten zu überwachen und zu steuern. Schädliche Apps können so das gesamte System beeinträchtigen. Diese Berechtigung wird nur zu Entwicklungszwecken und nie für die normale Nutzung benötigt."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"Broadcast ohne Paket senden"</string>
@@ -476,11 +475,11 @@
     <string name="permlab_controlWifiDisplay" msgid="393641276723695496">"WLAN-Anzeigen steuern"</string>
     <string name="permdesc_controlWifiDisplay" msgid="4543912292681826986">"Erlaubt der App, untergeordnete Funktionen von WLAN-Anzeigen zu steuern"</string>
     <string name="permlab_captureAudioOutput" msgid="6857134498402346708">"Audioausgabe erfassen"</string>
-    <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"Ermöglicht der App das Erfassen und Weiterleiten von Audioausgaben"</string>
+    <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"Ermöglicht der App die Erfassung und Weiterleitung von Audioausgaben"</string>
     <string name="permlab_captureVideoOutput" msgid="2246828773589094023">"Videoausgabe erfassen"</string>
-    <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"Ermöglicht der App das Erfassen und Weiterleiten von Videoausgaben"</string>
+    <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"Ermöglicht der App die Erfassung und Weiterleitung von Videoausgaben"</string>
     <string name="permlab_captureSecureVideoOutput" msgid="7815398969303382016">"Sichere Videoausgabe erfassen"</string>
-    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"Ermöglicht der App das Erfassen und Weiterleiten von sicheren Videoausgaben"</string>
+    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"Ermöglicht der App die Erfassung und Weiterleitung von sicheren Videoausgaben"</string>
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"Audio-Einstellungen ändern"</string>
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Ermöglicht der App, globale Audio-Einstellungen zu ändern, etwa die Lautstärke und den Lautsprecher für die Ausgabe."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"Audio aufnehmen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index c762d6f..7c46560 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"αποτροπή εναλλαγών εφαρμογών"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Δεν επιτρέπει στο χρήστη να μεταβεί σε άλλη εφαρμογή."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"λήψη πληροφοριών σχετικά με την τρέχουσα εφαρμογή"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Επιτρέπει στον κάτοχό του να ανακτήσει ιδιωτικές πληροφορίες σχετικά με την τρέχουσα εφαρμογή και τις υπηρεσίες στο προσκήνιο της οθόνης."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"παρακολούθηση και έλεγχος όλων των εκκινήσεων εφαρμογών"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Επιτρέπει στην εφαρμογή να παρακολουθεί και να ελέγχει τον τρόπο με τον οποίο το σύστημα εκκινεί δραστηριότητες. Τυχόν κακόβουλες εφαρμογές ενδέχεται να θέσουν σε κίνδυνο το σύστημα. Αυτή η άδεια είναι απαραίτητη μόνο για σκοπούς ανάπτυξης και ποτέ για συνήθη χρήση."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"αποστολή εκπομπής χωρίς πακέτο"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index ff26cf4..22281c1 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"prevent app switches"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Prevents the user from switching to another app."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"get current app info"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Allows the holder to retrieve private information about the current application and services in the foreground of the screen."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"monitor and control all app launching"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Allows the app to monitor and control how the system launches activities. Malicious apps may completely compromise the system. This permission is only needed for development, never for normal use."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"send package removed broadcast"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index e477ae8..9c1156bc 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"impedir conmutadores de aplicación"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Evita que el usuario cambie a otra aplicación."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"obtener información de aplicación actual"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permite que el titular recupere información privada sobre los servicios y la aplicación actuales en el primer plano de la pantalla."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"supervisar y controlar la ejecución de todas las aplicaciones"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permite que la aplicación supervise y controle la manera en la que el sistema inicia actividades. Las aplicaciones maliciosas pueden comprometer el sistema por completo. Este permiso es necesario solo para el desarrollo, nunca para el uso habitual."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"enviar emisión de paquete eliminado"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index f3c9903..d64a07a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"evitar cambios de aplicación"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Evita que el usuario cambie a otra aplicación."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"obtener información de la aplicación actual"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permite recuperar información privada sobre los servicios y la aplicación actuales en el primer plano de la pantalla."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"supervisar y controlar la ejecución de todas las aplicaciones"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permite que la aplicación supervise y controle la ejecución de las actividades del sistema. Las aplicaciones malintencionadas pueden vulnerar la seguridad del sistema. Este permiso es necesario únicamente para tareas de desarrollo, nunca para el uso habitual."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"enviar emisión eliminada de paquete"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index bf71a02..386ea2d 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"väldi rakenduste ümberlülitamist"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Takistab kasutaja lülitumist teisele rakendusele."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"aktiivse rakenduse teabe hankimine"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Lubab õiguse omanikul hankida privaatset teavet ekraani esiplaanil oleva aktiivse rakenduse ja teenuste kohta."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"Kõigi rakenduste käivitumise jälgimine ja juhtimine"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Võimaldab rakendusel jälgida ja juhtida, kuidas süsteem tegevusi käivitab. Pahatahtlikud rakendused võivad süsteemi täielikult rikkuda. Seda õigust on vaja ainult arenduseks, mitte tavakasutuse korral."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"saada paketist eemaldatud saade"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Võimaldab omanikul siduda märguannete kuulamisteenuse ülemise taseme kasutajaliidese. Seda ei tohiks tavarakenduste puhul kunagi vaja olla."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"operaatoripoolse konfiguratsioonirakenduse aktiveerimine"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Lubab omanikul aktiveerida operaatoripoolse konfiguratsioonirakenduse. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"võrgutingimuste teabe kuulamine"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Lubab rakendusel kuulata võrgutingimuste teavet. Ei ole kunagi vajalik tavaliste rakenduste puhul."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Parooli reeglite määramine"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrollige ekraaniluku avamise paroolide pikkust ja tähemärke."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Ekraani avamiskatsed"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 8aa97cd..136e027 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"ممانعت از جابجایی برنامه"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"اجازه نمی‎دهد کاربر به برنامه دیگری برود."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"دریافت اطلاعات برنامه فعلی"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"به دارنده اجازه می‌دهد اطلاعات خصوصی مربوط به خدمات و برنامه فعلی را در پیش‌زمینه صفحه بازیابی کند."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"نظارت و کنترل راه‌اندازی همه برنامه"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"به برنامه اجازه می‎دهد تا نحوه راه‌اندازی فعالیت‌های سیستم را کنترل کند. برنامه‎های مخرب می‎توانند کاملا با سیستم سازگار شوند. این مجوز فقط برای توسعه نیاز است و برای استفاده عادی نیست."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"ارسال پخش بسته حذف شده"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"به دارنده اجازه می‌دهد به یک رابط سطح بالای سرویس شنونده اعلان متصل شود. هرگز نباید برای برنامه‌های عادی لازم شود."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"لغو برنامه پیکربندی ارائه شده توسط شرکت مخابراتی"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"به دارنده اجازه می‌دهد که تنظیمات برنامه شرکت مخابراتی را لغو کند. هرگز برای برنامه‌های معمولی مورد نیاز نیست."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"گوش دادن برای بررسی شرایط شبکه"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"به برنامه امکان می‌دهد برای بررسی شرایط شبکه گوش دهد. این امکان هرگز نباید برای برنامه‌های معمولی مورد نیاز باشد."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"تنظیم قوانین رمز ورود"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"طول و نویسه‎های مجاز در گذرواژه‌های بازکردن قفل صفحه را کنترل کنید."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"نمایش تلاش‌های قفل گشایی صفحه"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index bdc8797..098be12 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"estä sovellusten vaihto"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Estää käyttäjää siirtymästä toiseen sovellukseen."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"hae nykyisen sovelluksen tiedot"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Antaa sovellukselle luvan noutaa nykyistä sovellusta ja nykyisiä palveluita koskevia yksityisiä tietoja ruudun etualalla."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"kaikkien sovellusten käynnistämisen valvonta ja hallinta"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Antaa sovelluksen valvoa ja hallita sitä, miten laite käynnistää toimintoja. Haitalliset sovellukset voivat vaarantaa laitteen käytön. Tätä oikeutta tarvitaan vain kehityskäyttöön eikä koskaan tavalliseen käyttöön."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"lähetä paketeista poistettuja lähetyksiä"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index bca53e1..9103e58 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"empêcher les changements d\'applications"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Empêche l\'utilisateur de changer d\'application."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"Récupérer des informations sur l\'application actuelle"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permet à l\'application autorisée de récupérer des informations confidentielles à propos de l\'application et des services exécutés au premier plan sur l\'écran."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"suivre et contrôler le lancement de toutes les applications"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permet à l\'application de surveiller et de contrôler la façon dont le système lance les activités. Des applications malveillantes peuvent exploiter cette fonctionnalité pour totalement compromettre le système. Cette autorisation est uniquement destinée aux développeurs. Elle ne doit jamais être activée dans le cadre d\'une utilisation standard."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"Envoyer une diffusion sans paquet"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index d4f10f1..07376ef 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"एप्‍लिकेशन स्‍विच करने से रोकता है"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"उपयोगकर्ता को दूसरे एप्‍लिकेशन पर स्‍विच करने से रोकता है."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"वर्तमान एप्लिकेशन की जानकारी प्राप्त करें"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"धारक को स्क्रीन की अग्रभूमि में वर्तमान एप्लिकेशन और सेवाओं की निजी जानकारी प्राप्त करने देती है."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"सभी एप्‍लिकेशन की लॉन्‍चिंग की निगरानी करें और उसे नियंत्रित करें"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"एप्लिकेशन को यह निगरानी और नियंत्रित करने देता है कि सिस्टम कैसे गतिविधियां लॉन्च करता है. दुर्भावनापूर्ण एप्लिकेशन सिस्टम को पूरी तरह से जोखिम में डाल सकते हैं. इस अनुमति की आवश्यकता केवल विकास के लिए है, सामान्य उपयोग के लिए कभी नहीं."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"पैकेज निकाले गए प्रसारण भेजें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 0d7d9bf..4cea5a6 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"sprečavanje promjene aplikacije"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Sprječava korisnika u prebacivanju na drugu aplikaciju."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"dohvaćanje informacija o trenutačnoj aplikaciji"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Omogućuje vlasniku dohvaćanje privatnih informacija o trenutačnoj aplikaciji i uslugama u prednjem planu na zaslonu."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"praćenje i nadzor svih pokretanja aplikacija"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Omogućuje aplikaciji nadzor i upravljanje načinom na koji sustav pokreće aktivnosti. Zlonamjerne aplikacije mogu posve ugroziti sustav. Ta je dozvola potrebna samo za razvoj, nikada za uobičajenu upotrebu."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"slanje paketno uklonjenog prijenosa"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Nositelju omogućuje vezanje uz sučelje najviše razine usluge slušatelja obavijesti. Ne bi smjelo biti potrebno za uobičajene aplikacije."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"pozovi operaterovu aplikaciju za konfiguraciju"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Dopušta nositelju pozivanje operaterove aplikacije za konfiguraciju. Ne bi smjelo biti potrebno za uobičajene aplikacije."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"praćenje motrenja mrežnih uvjeta"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Omogućuje aplikaciji praćenje motrenja mrežnih uvjeta. Ne bi trebalo biti potrebno za uobičajene aplikacije."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Postavi pravila zaporke"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Upravljajte duljinom zaporki za otključavanje zaslona i dopuštenim znakovima u tim zaporkama."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Nadgledaj pokušaje otključavanja zaslona"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index d9d570c..5e0fbc2 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"alkalmazásváltás megakadályozása"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Megakadályozza, hogy a felhasználó átváltson egy másik alkalmazásra."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"az alkalmazás aktuális információinak lekérése"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Lehetővé teszi, hogy a felhasználó privát adatokat kérjen le az aktuális alkalmazásról és szolgáltatásról a képernyő előterében."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"alkalmazásindítások nyomon követése és vezérlése"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Lehetővé teszi az alkalmazás számára, hogy figyelje és vezérelje, hogy a rendszer hogyan indít el tevékenységeket. A rosszindulatú alkalmazások teljesen tönkretehetik a rendszert. Ez az engedély csak fejlesztéshez szükséges, normál használathoz sosem."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"eltávolított csomagú üzenetek küldése"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 9892d10..1df979b 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"cegah pergantian aplikasi"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Mencegah pengguna beralih ke apl lain."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"dapatkan info tentang aplikasi yang aktif"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Mengizinkan pemegang mengambil informasi pribadi tentang aplikasi dan layanan saat ini di latar depan layar."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"memantau dan mengontrol semua peluncuran apl"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Mengizinkan apl memantau dan mengontrol cara sistem meluncurkan kegiatan. Apl berbahaya dapat meretas sistem sepenuhnya. Izin ini hanya diperlukan untuk pengembangan, tidak pernah diperlukan untuk penggunaan normal."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"kirim siaran paket dihapus"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Memungkinkan pemegang mengikat antarmuka tingkat teratas dari suatu layanan pendengar pemberitahuan. Tidak pernah diperlukan oleh aplikasi normal."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"memanggil aplikasi konfigurasi yang disediakan operator"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Memungkinkan pemegang meminta aplikasi konfigurasi yang disediakan operator. Tidak pernah diperlukan aplikasi normal."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"mendengar untuk observasi kondisi jaringan"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Memungkinkan aplikasi mendengar untuk observasi kondisi jaringan. Tidak pernah dibutuhkan oleh aplikasi normal."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Setel aturan sandi"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrol panjang dan karakter yang diizinkan dalam sandi pembuka layar."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Upaya pembukaan kunci layar monitor"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 4a5c12b..82cb917 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"impedire commutazione applicazione"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Impedisce all\'utente di passare a un\'altra applicazione."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"recupero di informazioni sull\'app corrente"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Consente al titolare di recuperare le informazioni private sull\'applicazione e sui servizi attualmente in primo piano sullo schermo."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"monitoraggio e controllo avvio di tutte le applicazioni"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Consente all\'applicazione di monitorare e controllare l\'avvio delle attività da parte del sistema. Le applicazioni dannose potrebbero compromettere completamente il sistema. Questa autorizzazione è necessaria solo per lo sviluppo, mai per l\'utilizzo normale."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"invio broadcast rimossi dal pacchetto"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 2c052b7..cef252a 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"מנע החלפת יישומים"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"מניעת מעבר ליישום אחר על ידי המשתמש."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"קבל פרטים על היישום הנוכחי"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"מאפשר לבעלים לאחזר מידע פרטי לגבי האפליקציה והשירותים הנוכחיים שבקדמת המסך."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"ניהול מעקב ושליטה על כל הפעלות היישומים"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"מאפשר ליישום לנהל מעקב אחר האופן שבו המערכת מפעילה פעילויות, ולשלוט בו. יישומים זדוניים עלולים לסכן את המערכת כולה. הרשאה זו אינה נחוצה לשימוש רגיל, אלא לפיתוח בלבד."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"שלח שידור שהוסר מחבילה"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 031d7f1..de0369c 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"アプリケーションの切り替えを禁止する"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"ユーザーが別のアプリに切り替えられないようにします。"</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"現在のアプリ情報の取得"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"画面のフォアグラウンドで実行されるアプリとサービスに関する非公開情報を取得することを所有者に許可します。"</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"すべてのアプリ起動の監視と制御"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"システムによるアクティビティ起動方法を監視し制御することをアプリに許可します。この許可を悪意のあるアプリに利用されると、システム全体のセキュリティが侵害される恐れがあります。この許可は開発時にのみ必要で、通常の使用では不要です。"</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"パッケージ削除ブロードキャストの送信"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 7d4e407..6477258 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"애플리케이션 전환 방지"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"사용자가 다른 앱으로 전환하지 못하게 합니다."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"현재 앱 정보 얻기"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"권한을 가진 프로그램이 화면의 포그라운드에서 현재 애플리케이션 및 서비스에 대한 비공개 정보를 검색하도록 허용합니다."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"실행 중인 모든 앱 모니터링 및 제어"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"앱이 시스템에서 활동이 시작되는 방식을 모니터링하고 관리할 수 있도록 허용합니다. 이 경우 악성 앱이 이 기능을 이용하여 시스템을 완전히 손상시킬 수 있습니다. 이 권한은 개발 과정에만 필요하며 일반 사용 시에는 필요하지 않습니다."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"패키지 제거 브로드캐스트 보내기"</string>
@@ -477,10 +476,10 @@
     <string name="permdesc_controlWifiDisplay" msgid="4543912292681826986">"앱이 Wi-Fi 디스플레이의 하위 수준 기능을 제어하도록 허용합니다."</string>
     <string name="permlab_captureAudioOutput" msgid="6857134498402346708">"오디오 출력 캡처"</string>
     <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"앱이 오디오 출력을 캡처하고 리디렉션하도록 허용합니다."</string>
-    <string name="permlab_captureVideoOutput" msgid="2246828773589094023">"비디오 출력 캡처"</string>
-    <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"앱이 비디오 출력을 캡처하고 리디렉션하도록 허용합니다."</string>
-    <string name="permlab_captureSecureVideoOutput" msgid="7815398969303382016">"안전한 비디오 출력 캡처"</string>
-    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"앱이 안전한 비디오 출력을 캡처하고 리디렉션하도록 허용합니다."</string>
+    <string name="permlab_captureVideoOutput" msgid="2246828773589094023">"동영상 출력 캡처"</string>
+    <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"앱이 동영상 출력을 캡처하고 리디렉션하도록 허용합니다."</string>
+    <string name="permlab_captureSecureVideoOutput" msgid="7815398969303382016">"안전한 동영상 출력 캡처"</string>
+    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"앱이 안전한 동영상 출력을 캡처하고 리디렉션하도록 허용합니다."</string>
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"오디오 설정 변경"</string>
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"앱이 음량이나 출력을 위해 사용하는 스피커 등 전체 오디오 설정을 변경할 수 있도록 허용합니다."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"오디오 녹음"</string>
diff --git a/packages/SystemUI/res/values-land/refs.xml b/core/res/res/values-land/refs.xml
similarity index 83%
rename from packages/SystemUI/res/values-land/refs.xml
rename to core/res/res/values-land/refs.xml
index 62fb77d..cda38cf 100644
--- a/packages/SystemUI/res/values-land/refs.xml
+++ b/core/res/res/values-land/refs.xml
@@ -16,5 +16,5 @@
 */
 -->
 <resources>
-    <item type="string" name="hiding_navigation_confirmation_message">@string/hiding_navigation_confirmation_message_long</item>
-</resources>
+    <item type="string" name="transient_navigation_confirmation">@string/transient_navigation_confirmation_long</item>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 544f98b..1158a26 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"neleisti perjungti programų"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Neleidžiama naudotojui perjungti į kitą programą."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"gauti esamos programos informaciją"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Savininkui leidžiama gauti privačią informaciją apie dabartinę programą ir paslaugas, naudojamas ekrano priekiniame plane."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"stebėti ir valdyti visų programų paleidimą"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Leidžiama programai stebėti ir valdyti, kaip sistema paleidžia veiklą. Kenkėjiškos programos gali visiškai pažeisti sistemą. Šis leidimas reikalingas tik kuriant ir jo niekada nereikia naudojant įprastai."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"siųsti pašalinto paketo perdavimą"</string>
@@ -475,7 +474,7 @@
     <string name="permdesc_configureWifiDisplay" msgid="7916815158690218065">"Leidžiama programai konfigūruoti ir prisijungti prie „Wi-Fi“ pateikčių."</string>
     <string name="permlab_controlWifiDisplay" msgid="393641276723695496">"valdyti „Wi-Fi“ pateiktis"</string>
     <string name="permdesc_controlWifiDisplay" msgid="4543912292681826986">"Leidžiama programai valdyti „Wi-Fi“ pateikčių žemo lygio funkcijas."</string>
-    <string name="permlab_captureAudioOutput" msgid="6857134498402346708">"fiksuoti garso įvestį"</string>
+    <string name="permlab_captureAudioOutput" msgid="6857134498402346708">"fiksuoti garso išvestį"</string>
     <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"Programai leidžiama fiksuoti ir peradresuoti garso išvestį."</string>
     <string name="permlab_captureVideoOutput" msgid="2246828773589094023">"fiksuoti vaizdo išvestį"</string>
     <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"Programai leidžiama fiksuoti ir peradresuoti vaizdo išvestį."</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Leidžiama turėtojui susisaistyti su pranešimų skaitymo priemonės paslaugos aukščiausio lygio sąsaja. Įprastoms programoms to neturėtų prireikti."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"iškviesti operatoriaus pateiktą konfigūravimo programą"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Turėtojui leidžiama iškviesti operatoriaus pateiktą konfigūravimo programą. Įprastoms programoms to neturėtų prireikti."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"vykdyti tinklo sąlygų stebėjimą"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Leidžiama programai vykdyti tinklo sąlygų stebėjimą. To niekada neturėtų prireikti naudojant įprastas programas."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Nustatyti slaptažodžio taisykles"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Valdyti leidžiamą ekrano atrakinimo slaptažodžių ilgį ir leidžiamus naudoti simbolius."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Stebėti bandymus atrakinti ekraną"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 83d3962..12acf7a 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"novērst lietojumprogrammu pārslēgšanu"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Novērš lietotāja pārslēgšanos uz citu lietotni."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"pašreizējās lietotnes informācijas iegūšana"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Ļauj īpašniekam izgūt privātu informāciju par pašreizējo lietojumprogrammu un pakalpojumiem ekrāna priekšplānā."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"pārraudzīt un kontrolēt visu lietotņu atvēršanu"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Ļauj lietotnei pārraudzīt un kontrolēt, kā sistēmā tiek palaistas darbības. Ļaunprātīgas lietotnes var pilnībā uzlauzt sistēmu. Šī atļauja ir nepieciešama tikai izstrādei, taču ne parastai lietošanai."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"sūtīt apraidi par pakotnes noņemšanu"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Ļauj īpašniekam izveidot saiti ar paziņojumu uztvērēja pakalpojuma augšējā līmeņa saskarni. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"Operatora nodrošinātas konfigurācijas lietotnes izsaukšana"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Ļauj īpašniekam izsaukt operatora nodrošināto konfigurācijas lietotni. Parastām lietotnēm tas nekad nav nepieciešams."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"iegūt informāciju par tīkla stāvokļa novērojumiem"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Ļauj lietojumprogrammai iegūt informāciju par tīkla stāvokļa novērojumiem. Parastām lietotnēm šī atļauja nekad nav nepieciešama."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Paroles kārtulu iestatīšana"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrolē ekrāna atbloķēšanas parolē atļautās rakstzīmes un garumu."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Ekrāna atbloķēšanas mēģinājumu pārraudzīšana"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index c89ffe8..bb12579 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"halang pertukaran apl"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Menghalang pengguna daripada bertukar kepada apl lain."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"dapatkan maklumat apl semasa"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Membenarkan pemegang mendapatkan maklumat peribadi tentang aplikasi dan perkhidmatan semasa di latar hadapan skrin."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"pantau dan kawal semua pelancaran apl"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Membenarkan apl untuk memantau dan mengawal cara sistem melancarkan aktiviti. Apl hasad boleh menjejaskan sistem sepenuhnya. Kebenaran ini hanya diperlukan untuk pembangunan, tidak sekali-kali untuk penggunaan biasa."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"hantar siaran bahawa pakej telah dialih keluar"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 75362a4..e55e6db 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"forhindre bytte mellom apper"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Hindrer brukeren i å bytte til en annen app."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"få informasjon om appen"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Tillater innehaveren å hente privat informasjon om den gjeldende appen og tjenester i forgrunnen av skjermen."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"overvåke og kontrollere all oppstart av apper"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Lar appen overvåke og kontrollere hvordan systemet starter opp aktiviteter. Ondsinnede apper kan utsette hele systemet for sikkerhetsbrudd. Denne tillatelsen er bare nødvendig for utviklere, aldri for vanlig bruk."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"kringkaste melding om fjernet pakke"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 9c4e213..d48ccb8 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"schakelen tussen apps voorkomen"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Hiermee wordt voorkomen dat de gebruiker overschakelt naar een andere app."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"huidige appgegevens ophalen"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Hiermee kan de houder persoonlijke gegevens ophalen over de app en services die momenteel op de voorgrond worden weergegeven."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"alle startende apps bijhouden en beheren"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Hiermee kan de app de manier bijhouden en beheren waarop het systeem activiteiten start. Schadelijke apps kunnen het systeem volledig in gevaar brengen. Deze machtiging is alleen voor ontwikkeling vereist, nooit voor normaal gebruik."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"melding verzenden dat pakket is verwijderd"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index d4a5761..297bc53 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"zapobieganie przełączaniu aplikacji"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Uniemożliwia użytkownikowi przełączenie na inną aplikację."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"pobierz informacje o bieżącej aplikacji"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Pozwala właścicielowi na pobieranie prywatnych informacji o bieżącej aplikacji i usługach widocznych na ekranie."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"monitorowanie i kontrolowanie wszystkich uruchamianych aplikacji"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Pozwala aplikacji na monitorowanie i kontrolowanie sposobu uruchamiania działań przez system. Złośliwe aplikacje mogą całkowicie naruszyć zabezpieczenia systemu. To uprawnienie nigdy nie jest potrzebne podczas normalnego użytkowania, a jedynie podczas programowania."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"wysyłanie transmisji informującej o usuniętym pakiecie"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 2db7a18..00eb416 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"impedir trocas de aplicações"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Impede que o utilizador mude para outra aplicação."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"obter informações da aplicação atual"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permite ao titular obter informações privadas acerca da aplicação e dos serviços atuais no primeiro plano do ecrã."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"monitorizar e controlar a iniciação de todas as aplicações"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permite que uma aplicação monitorize e controle a forma como o sistema inicia atividades. As aplicações maliciosas podem comprometer totalmente o sistema. Esta autorização só é necessária para programação, nunca para utilização normal."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"enviar difusão de pacote removido"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 2fbb511..e5285b1 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"evitar trocas de aplicativo"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Impede que o usuário alterne para outro aplicativo."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"obter informações do aplicativo atual"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permite que o proprietário armazene informações particulares sobre o aplicativo e os serviços em primeiro plano na tela."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"monitorar e controlar todos os aplicativos que estão sendo iniciados"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permite que o aplicativo monitore e controle a forma como o sistema inicia atividades. Aplicativos maliciosos podem comprometer completamente o sistema. Esta permissão só é necessária para o desenvolvimento, nunca para o uso normal."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"enviar transmissão removida do pacote"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 11f4b5f3..6fbecc8 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"împiedicare comutare între aplicaţii"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Împiedică trecerea utilizatorului la o altă aplicaţie."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"obținere informații despre aplicația curentă"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Permite titularului să recupereze informații private despre aplicația și serviciile curente în prim-planul ecranului."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"monitorizare şi control asupra lansării tuturor aplicaţiilor"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Permite aplicaţiei să monitorizeze şi să controleze modul în care sistemul lansează activităţi. Aplicaţiile rău intenţionate pot să compromită sistemul în întregime. Această permisiune este necesară doar pentru dezvoltare şi niciodată pentru utilizarea normală."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"trimitere mesaj difuzat privind extragerea din pachet"</string>
@@ -475,12 +474,12 @@
     <string name="permdesc_configureWifiDisplay" msgid="7916815158690218065">"Permite aplicaţiei să configureze şi să se conecteze la afişaje Wi-Fi."</string>
     <string name="permlab_controlWifiDisplay" msgid="393641276723695496">"controlează afişaje Wi-Fi"</string>
     <string name="permdesc_controlWifiDisplay" msgid="4543912292681826986">"Permite aplicaţiei să controleze funcţiile de nivel redus ale afişajelor Wi-Fi."</string>
-    <string name="permlab_captureAudioOutput" msgid="6857134498402346708">"capturați ieșirea audio"</string>
-    <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"Permite aplicației să captureze și să redirecționeze ieșirea audio."</string>
-    <string name="permlab_captureVideoOutput" msgid="2246828773589094023">"capturați ieșirea video"</string>
-    <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"Permite aplicației să captureze și să redirecționeze ieșirea video."</string>
-    <string name="permlab_captureSecureVideoOutput" msgid="7815398969303382016">"capturați ieșirea video securizată"</string>
-    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"Permite aplicației să captureze și să redirecționeze ieșirea video securizată."</string>
+    <string name="permlab_captureAudioOutput" msgid="6857134498402346708">"să intercepteze ieșirea audio"</string>
+    <string name="permdesc_captureAudioOutput" msgid="6210597754212208853">"Permite aplicației să intercepteze și să redirecționeze ieșirea audio."</string>
+    <string name="permlab_captureVideoOutput" msgid="2246828773589094023">"să intercepteze ieșirea video"</string>
+    <string name="permdesc_captureVideoOutput" msgid="359481658034149860">"Permite aplicației să intercepteze și să redirecționeze ieșirea video."</string>
+    <string name="permlab_captureSecureVideoOutput" msgid="7815398969303382016">"să intercepteze ieșirea video securizată"</string>
+    <string name="permdesc_captureSecureVideoOutput" msgid="2779793064709350289">"Permite aplicației să intercepteze și să redirecționeze ieșirea video securizată."</string>
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"modificare setări audio"</string>
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permite aplicaţiei să modifice setările audio globale, cum ar fi volumul şi difuzorul care este utilizat pentru ieşire."</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"înregistrare audio"</string>
@@ -654,7 +653,7 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Permite proprietarului să se conecteze la interfața de nivel superior a unui serviciu de citire a notificărilor. În mod normal aplicațiile nu ar trebui să aibă nevoie de această permisiune."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"apelarea aplicației de configurare furnizată de operator"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Permite proprietarului să apeleze aplicația de configurare furnizată de operator. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
-    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"ascultați observații despre starea rețelei"</string>
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"ascultă observații despre starea rețelei"</string>
     <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Permite unei aplicații să asculte observații despre starea rețelei. Nu ar trebui să fie necesară pentru aplicațiile obișnuite."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Setaţi reguli pentru parolă"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Stabiliţi lungimea şi tipul de caractere permise în parolele pentru deblocarea ecranului."</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 1629be3..add1291 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"Защита от переключения приложений"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Запрещает пользователям переключаться между приложениями."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"Показ информации о текущем приложении"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"На экране будут отображаться сведения о текущем приложении и запущенных сервисах."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"Отслеживание и управление запуском всех приложений"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Приложение сможет отслеживать запуск системных процессов и управлять им. Вредоносные программы смогут получить полный контроль над системой. Это разрешение необходимо только для разработки и не нужно в обычном режиме."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"Отправка оповещений об удалении пакетов"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Приложение сможет подключаться к базовому интерфейсу службы просмотра уведомлений. Это разрешение не используется обычными приложениями."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"Запуск приложения настроек, предоставленного оператором"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Владелец сможет запускать приложение настроек, предоставленное оператором. Это разрешение не используется обычными приложениями."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"Использование данных о состоянии сети"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Приложение сможет использовать данные о состоянии сети. Это разрешение обычно используется только специальными приложениями."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Правила выбора паролей"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Контролировать длину и символы при вводе паролей для снятия блокировки экрана."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Отслеживать попытки снятия блокировки экрана"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 8eb65a6..c33a7ea 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"zabrániť prepínaniu aplikácií"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Zabráni používateľovi prepnúť na inú aplikáciu."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"získať informácie o aktuálnej aplikácii"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Umožňuje držiteľovi povolenia načítať súkromné informácie o aktuálnej aplikácii a službách v popredí obrazovky."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"sledovať a ovládať všetky spustenia aplikácií"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Umožňuje aplikácii sledovať a ovládať spúšťanie aktivít systémom. Škodlivé aplikácie môžu systém úplne ovládnuť. Toto povolenie je potrebné len na účely vývoja, nikdy nie na bežné používanie."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"odoslanie vysielania o odstránení balíčka"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 6ee87fc..45cb215 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"preprečevanje preklopa programov"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Uporabniku preprečuje preklop v drug program."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"pridobivanje podatkov o trenutni aplikaciji"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Imetniku dovoli prenos zasebnih podatkov o trenutni aplikaciji in storitvah v ospredju zaslona."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"spremljanje in nadzor vseh zagonov programov"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Programu omogoča spremljanje in nadziranje načina, kako sistem zažene dejavnosti. Zlonamerni programi lahko v celoti ogrozijo varnost sistema. To dovoljenje je potrebno samo za razvoj, vendar nikoli za običajno uporabo."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"pošiljanje oddaje brez paketa"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 85bac70..404590d 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"спречавање пребацивања са једне апликације на другу"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Спречава да корисник пређе на другу апликацију."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"преузимање информација о актуелној апликацији"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Дозвољава власнику да преузима приватне информације о актуелној апликацији и услугама у првом плану екрана."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"надгледање и контрола покретања свих апликација"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Дозвољава апликацији да прати начин на који систем покреће активности и да њиме управља. Злонамерне апликације могу у потпуности да угрозе систем. Ова дозвола је потребна само за програмирање, а никада за уобичајено коришћење."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"слање емитовања уклоњеног пакета"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Дозвољава власнику да се повеже са интерфејсом услуге монитора обавештења највишег нивоа. Уобичајене апликације никада не би требало да је користе."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"позивање апликације са конфигурацијом коју одређује оператер"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Дозвољава власнику да позива апликацију са конфигурацијом коју одређује оператер. Уобичајене апликације никада не би требало да је користе."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"праћење података о условима на мрежи"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Дозвољава апликацији да прати податке о условима на мрежи. Не би никада требало да буде потребно за нормалне апликације."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Подешавање правила за лозинку"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Контролишите дужину и знакове дозвољене у лозинкама за откључавање екрана."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Надгледање покушаја откључавања екрана"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 763653d..221ddb6 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"förhindrar programbyten"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Hindrar användaren från att byta till en annan app."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"hämta information om aktuell app"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Innehavaren tillåts att hämta privat information om den app och de tjänster som för tillfället är i förgrunden på skärmen."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"övervaka och styra alla appar som öppnas"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Tillåter att appen övervakar och styr hur systemet startar aktiviteter. Skadliga appar kan kompromettera systemet helt. Den här behörigheten behövs bara för programmering, aldrig för vanlig användning."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"skicka meddelande om borttaget paket"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 23ca2dd..767bf44 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"zuia swichi za app"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Huzuia mtumiaji dhidi ya kubadilisha na kwenda kwa programu nyingine."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"pata maelezo ya sasa kuhusu programu"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Huruhusu mmiliki kurejesha maelezo binafsi kuhusu programu ya sasa na huduma katika mandharimbele ya skrini."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"Fuatilia na kudhibiti uzinduzi wote wa programu"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Inaruhusu programu kufuatilia na kudhibiti jinsi mfumo unazindua shughuli. Programu hasidi zinaweza kutia mfumo hatarini. Ruhusa inahitajika tu kwa usanidi, kamwe sio kwa matumizi ya kawaida."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"furushi lililotumwa limeondoa tangazo"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Inaruhusu kishikilizi kuunganishwa kwenye kusano cha kiwango cha juu cha huduma ya kisikilizi cha arifa. Haipaswi kuhitajika tena kwa programu za kawaida."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"omba programu ya usakinishaji inayotolewa na mtoa huduma."</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Inaruhusu kishikiliaji kuomba programu ya usakinishaji inayotolewa na mto huduma. Haipaswi kuhitajika kwa programu za kawaida."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"sikiliza matukio katika hali za mtandao"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Huruhusu programu kusikiliza matukio katika hali za mtandao. Haipaswi kuhitajika kamwe kwa programu za kawaida."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Weka kanuni za nenosiri"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Dhibiti urefu na vibambo vinavyoruhusiwa katika manenosiri ya kufungua skrini."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Chunguza majaribio ya kutofun gua skrini"</string>
diff --git a/packages/SystemUI/res/values-land/refs.xml b/core/res/res/values-sw600dp-port/refs.xml
similarity index 83%
copy from packages/SystemUI/res/values-land/refs.xml
copy to core/res/res/values-sw600dp-port/refs.xml
index 62fb77d..cda38cf 100644
--- a/packages/SystemUI/res/values-land/refs.xml
+++ b/core/res/res/values-sw600dp-port/refs.xml
@@ -16,5 +16,5 @@
 */
 -->
 <resources>
-    <item type="string" name="hiding_navigation_confirmation_message">@string/hiding_navigation_confirmation_message_long</item>
-</resources>
+    <item type="string" name="transient_navigation_confirmation">@string/transient_navigation_confirmation_long</item>
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 98b9950..03d0907 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"ป้องกันการเปลี่ยนแอปพลิเคชัน"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"ป้องกันไม่ให้ผู้ใช้สลับไปใช้แอปพลิเคชันอื่น"</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"รับข้อมูลแอปพลิเคชันปัจจุบัน"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"ช่วยให้เจ้าของสามารถดึงข้อมูลส่วนตัวเกี่ยวกับแอปพลิเคชันและบริการปัจจุบันในส่วนหน้าของหน้าจอ"</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"ตรวจสอบและควบคุมแอปพลิเคชันทั้งหมดที่เปิดใช้งาน"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"อนุญาตให้แอปพลิเคชันตรวจสอบและควบคุมวิธีการที่ระบบเปิดกิจกรรมต่างๆ แอปพลิเคชันที่เป็นอันตรายอาจทำอันตรายแก่ระบบได้อย่างสิ้นเชิง การอนุญาตนี้จำเป็นสำหรับการพัฒนาเท่านั้น ไม่ใช้สำหรับแอปพลิเคชันทั่วไปโดยเด็ดขาด"</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"ส่งการกระจายข้อมูลว่ามีการนำแพคเกจออก"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"อนุญาตให้เจ้าของเชื่อมโยงกับอินเตอร์เฟซระดับสูงสุดของบริการตัวฟังการแจ้งเตือน ซึ่งไม่มีความจำเป็นสำหรับแอปธรรมดา"</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"เรียกใช้แอปการกำหนดค่าของผู้ให้บริการ"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"อนุญาตให้ผู้ใช้สามารถเรียกใช้แอปการกำหนดค่าของผู้ให้บริการ ซึ่งแอปทั่วไปไม่จำเป็นต้องใช้"</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"ฟังข้อสังเกตเกี่ยวกับสภาวะของเครือข่าย"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"อนุญาตให้แอปพลิเคชันฟังข้อสังเกตเกี่ยวกับสภาวะของเครือข่าย ไม่จำเป็นสำหรับแอปปกติ"</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"ตั้งค่ากฎรหัสผ่าน"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"ควบคุมความยาวและอักขระที่อนุญาตให้ใช้ในรหัสผ่านการปลดล็อกหน้าจอ"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"ตรวจสอบความพยายามในการปลดล็อกหน้าจอ"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 01aab09..e3f9775 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"pigilan ang mga paglipat ng app"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Pinipigilan ang mga user sa paglipat sa isa pang app."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"kunin ang impormasyon ng kasalukuyang app"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Pinapayagan ang may-ari na kumuha ng pribadong impormasyon tungkol sa kasalukuyang application at mga serbisyo sa foreground ng screen."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"subaybayan at kontrolin ang lahat ng paglunsad ng app"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Pinapayagan ang app na subaybayan at kontrolin kung paano naglulunsad ng mga aktibidad ang system. Maaaring ganap na ikompromiso ng nakakahamak na apps ang system. Kinakailangan lamang ang pahintulot na ito para sa pagpapabuti, hindi kailanman para sa normal na paggamit."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"magpadala ng package inalis ang broadcast"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Nagbibigay-daan sa may-ari na mapailalim sa interface sa tuktok na antas ng isang serbisyo ng notification listener. Hindi dapat kailanganin para sa karaniwang apps kahit kailan."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"paganahin ang app ng configuration na ibinigay ng carrier"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Nagbibigay-daan sa may-ari na paganahin ang app ng configuration na ibinigay ng carrier. Hindi dapat kailanganin para sa normal na apps kahit kailan."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"makinig sa mga obserbasyon sa mga kundisyon ng network"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Nagbibigay-daan sa isang application na makinig sa mga obserbasyon sa mga kundisyon ng network. Dapat na hindi kailanman kakailanganin para sa normal na apps."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Magtakda ng mga panuntunan sa password"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kontrolin ang haba at mga character na pinapayagan sa mga password sa pag-unlock ng screen."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Subaybayan ang mga pagsubok sa pag-unlock ng screen"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 1c8b640..925fea7 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"uygulama değişimlerini engelle"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Kullanıcının başka bir uygulamaya geçiş yapmasını engeller."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"geçerli uygulama bilgilerini al"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"İzin sahibine, ekranın ön planındaki geçerli uygulama ve hizmetler hakkında gizli bilgileri alma olanağı verir."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"tüm uygulama başlatma işlemlerini izle ve denetle"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Uygulamaya, sistemin etkinlikleri nasıl başlattığını izleme ve denetleme izni verir. Kötü amaçlı uygulamalar sistemi tamamen tehlikeye atabilir. Bu izin normal kullanım için değildir, sadece geliştirme süreçlerinde kullanılır."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"paket ile kaldırılan yayını gönder"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index e87a400..41ca8c6 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"запобіг. зміні програм"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Запобігати переходу користувача до іншої програми."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"отримати інформацію про поточну програму"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Дозволяє власникові отримувати приватну інформацію про поточну програму та служби на передньому плані екрана."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"відстежувати та контролювати запуски всіх програм"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Дозволяє програмі відстежувати та контролювати, як саме система запускає дії. Шкідливі програми можуть отримати повний контроль над системою. Цей дозвіл потрібний лише для розробки, а не для звичайного користування."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"надсил. запис про видал. пакета"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 2e95cd1..ef96b32 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"ngăn chuyển đổi ứng dụng"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Ngăn người dùng chuyển sang ứng dụng khác."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"truy cập thông tin ứng dụng hiện tại"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Cho phép chủ sở hữu truy xuất thông tin cá nhân về ứng dụng và dịch vụ hiện tại ở nền trước của màn hình."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"giám sát và kiểm soát tất cả hoạt động khởi chạy ứng dụng"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Cho phép ứng dụng giám sát và kiểm soát cách hệ thống khởi chạy các hoạt động. Ứng dụng độc hại hoàn toàn có thể làm tổn hại hệ thống. Quyền này chỉ cần cho mục đích phát triển, không dành cho mục đích sử dụng thông thường."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"gửi truyền phát đã xóa của gói"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Cho phép chủ sở hữu liên kết với giao diện cấp cao nhất của dịch vụ trình xử lý thông báo. Không cần thiết cho các ứng dụng thông thường."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"gọi ra ứng dụng cấu hình do nhà cung cấp dịch vụ cung cấp"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Cho phép chủ sở hữu gọi ra ứng dụng cấu hình do nhà cung cấp dịch vụ cung cấp. Không cần thiết cho các ứng dụng thông thường."</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"quan sát các điều kiện mạng"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"Cho phép ứng dụng quan sát các điều kiện mạng. Không bao giờ cần cho ứng dụng thông thường."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Đặt quy tắc mật khẩu"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"Kiểm soát độ dài và ký tự được phép trong mật khẩu mở khóa màn hình."</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"Giám sát những lần thử mở khóa màn hình"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index a2267a6..b4af5d0 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"禁止切换应用"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"阻止用户切换到其他应用。"</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"获取当前应用的信息"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"允许应用检索目前在屏幕前台运行的应用和服务专有的信息。"</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"监控所有应用的启动"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"允许应用监视和控制系统是如何启动活动的。恶意应用可能会完全破坏系统。此权限只有在进行开发时才需要,正常使用情况下绝不需要。"</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"发送包删除的广播"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 9558319..062e415 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"防止切換應用程式"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"防止使用者切換到其他應用程式。"</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"取得目前的應用程式資訊"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"允許應用程式針對目前在螢幕前景運作的應用程式與服務擷取相關私人資訊。"</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"監視及控制所有應用程式的啟動程序"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"允許應用程式監視和控制系統啟動活動的方式。請注意,惡意應用程式可能利用此功能破壞整個系統。這個權限只有開發人員才需要,一般使用者不需使用這個權限。"</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"傳送程式已移除廣播"</string>
@@ -654,10 +653,8 @@
     <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"允許應用程式繫結至通知接聽器服務的頂層介面 (一般應用程式不需使用)。"</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"叫用行動通訊業者提供的設定應用程式"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"允許應用程式叫用行動通訊業者提供的設定應用程式 (一般應用程式並不需要)。"</string>
-    <!-- no translation found for permlab_accessNetworkConditions (8206077447838909516) -->
-    <skip />
-    <!-- no translation found for permdesc_accessNetworkConditions (6899102075825272211) -->
-    <skip />
+    <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"監聽網路狀況觀察資訊"</string>
+    <string name="permdesc_accessNetworkConditions" msgid="6899102075825272211">"允許應用程式監聽網路狀況觀察資訊 (一般應用程式並不需要)。"</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"設定密碼規則"</string>
     <string name="policydesc_limitPassword" msgid="3252114203919510394">"控制螢幕解鎖密碼所允許的長度和字元。"</string>
     <string name="policylab_watchLogin" msgid="914130646942199503">"監視螢幕解鎖嘗試次數"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 575ff59..538299b 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -314,8 +314,7 @@
     <string name="permlab_stopAppSwitches" msgid="4138608610717425573">"gwema ukushintsha kohlelo lokusebenza"</string>
     <string name="permdesc_stopAppSwitches" msgid="8262195802582255021">"Igwema umsebenzisi ukuthi ashintshele kolunye uhlelo lokusebenza."</string>
     <string name="permlab_getTopActivityInfo" msgid="2537922311411546016">"thola ulwazi lohlelo lokusebenza lwamanje"</string>
-    <!-- no translation found for permdesc_getTopActivityInfo (8153651434145132505) -->
-    <skip />
+    <string name="permdesc_getTopActivityInfo" msgid="8153651434145132505">"Ivumela umphathi ukuthi athole ulwazi mayelana nohlelo lwakho lokusebenza lwamanje namasevisi ngaphambili kwesikrini."</string>
     <string name="permlab_runSetActivityWatcher" msgid="892239094867182656">"qapha futhi ulawule ukuqaliswa kwazo zonke izinsiza"</string>
     <string name="permdesc_runSetActivityWatcher" msgid="6003603162578577406">"Ivumela uhlelo lokusebebenza ukuthi luhlole futhi lulawule ukuthi isistimu iziqalisa kanjani imisebenzi. Izinhlelo zokusebenza ezinobungozi zingensa isistimu ibe sebungozini. Le mvume idingakalela intuthuku kuphela hhayi ukusetshenziswa okwejwayelekile."</string>
     <string name="permlab_broadcastPackageRemoved" msgid="2576333434893532475">"thumela iphakheji yomsakazo okhishiwe"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 721bc3f..03e9045 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5728,6 +5728,25 @@
     <declare-styleable name="SizeAdaptiveLayout" />
 
     <!-- =============================== -->
+    <!-- Location package class attributes -->
+    <!-- =============================== -->
+    <eat-comment />
+
+    <!-- Use <code>injected-location-setting</code> as the root tag of the XML resource that
+         describes an injected "Location services" setting. Note that the status value (subtitle)
+         for the setting is specified dynamically by a subclass of SettingInjectorService.
+     -->
+    <declare-styleable name="InjectedLocationSetting">
+        <!-- The user-visible name (title) of the setting. -->
+        <attr name="label"/>
+        <!-- The icon for the apps covered by the setting. Typically a generic icon for the
+             developer. -->
+        <attr name="icon"/>
+        <!-- The activity to launch when the setting is clicked on. -->
+        <attr name="settingsActivity"/>
+    </declare-styleable>
+
+    <!-- =============================== -->
     <!-- LockPatternView class attributes -->
     <!-- =============================== -->
     <eat-comment />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0908f36..400e892 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -353,6 +353,9 @@
          Default value is 2 minutes. -->
     <integer translatable="false" name="config_wifi_driver_stop_delay">120000</integer>
 
+    <!-- Wifi driver supports batched scan -->
+    <bool translatable="false" name="config_wifi_batched_scan_supported">false</bool>
+
     <!-- Flag indicating whether the we should enable the automatic brightness in Settings.
          Software implementation will be used if config_hardware_auto_brightness_available is not set -->
     <bool name="config_automatic_brightness_available">false</bool>
@@ -1200,4 +1203,7 @@
          from an emulated display within the physical display. -->
     <bool name="config_forceDefaultOrientation">false</bool>
 
+    <!-- Default Gravity setting for the system Toast view. Equivalent to: Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM -->
+    <integer name="config_toastDefaultGravity">0x00000051</integer>
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 138debf..e497c85 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4313,4 +4313,9 @@
         <item quantity="other">Incorrect PIN. Try again in <xliff:g id="count">%d</xliff:g> seconds.</item>
     </plurals>
 
+    <!-- Toast bar message when hiding the transient navigation bar [CHAR LIMIT=35] -->
+    <string name="transient_navigation_confirmation">Swipe edge of screen to reveal bar</string>
+
+    <!-- Longer version of toast bar message when hiding the transient navigation bar (if room) -->
+    <string name="transient_navigation_confirmation_long">Swipe from edge of screen to reveal system bar</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 04cdac9..14ae1e6 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -281,7 +281,8 @@
   <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" />
   <java-symbol type="bool" name="config_useFixedVolume" />
   <java-symbol type="bool" name="config_forceDefaultOrientation" />
-  
+  <java-symbol type="bool" name="config_wifi_batched_scan_supported" />
+
   <java-symbol type="integer" name="config_cursorWindowSize" />
   <java-symbol type="integer" name="config_extraFreeKbytesAdjust" />
   <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
@@ -294,6 +295,7 @@
   <java-symbol type="integer" name="config_ntpRetry" />
   <java-symbol type="integer" name="config_ntpThreshold" />
   <java-symbol type="integer" name="config_ntpTimeout" />
+  <java-symbol type="integer" name="config_toastDefaultGravity" />
   <java-symbol type="integer" name="config_wifi_framework_scan_interval" />
   <java-symbol type="integer" name="config_wifi_supplicant_scan_interval" />
   <java-symbol type="integer" name="config_wifi_scan_interval_p2p_connected" />
@@ -869,6 +871,8 @@
   <java-symbol type="string" name="restr_pin_enter_pin" />
   <java-symbol type="string" name="write_fail_reason_cancelled" />
   <java-symbol type="string" name="write_fail_reason_cannot_write" />
+  <java-symbol type="string" name="transient_navigation_confirmation" />
+  <java-symbol type="string" name="transient_navigation_confirmation_long" />
 
   <java-symbol type="plurals" name="abbrev_in_num_days" />
   <java-symbol type="plurals" name="abbrev_in_num_hours" />
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetEventsTest.java b/core/tests/coretests/src/android/animation/AnimatorSetEventsTest.java
index d415e4e..7eb32ee 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetEventsTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetEventsTest.java
@@ -37,14 +37,12 @@
         button = (Button) getActivity().findViewById(R.id.animatingButton);
         mAnimator = new AnimatorSet();
         ((AnimatorSet)mAnimator).playSequentially(xAnim, yAnim);
-
         super.setUp();
     }
 
     @Override
     protected long getTimeout() {
-        return (xAnim.getDuration() + yAnim.getDuration()) +
-                (xAnim.getStartDelay() + yAnim.getStartDelay()) +
+        return (2 * mAnimator.getDuration()) + (2 * mAnimator.getStartDelay()) +
                 ANIM_DELAY + FUTURE_RELEASE_DELAY;
     }
 
diff --git a/core/tests/coretests/src/android/animation/EventsTest.java b/core/tests/coretests/src/android/animation/EventsTest.java
index 8df711b..28cfe3d 100644
--- a/core/tests/coretests/src/android/animation/EventsTest.java
+++ b/core/tests/coretests/src/android/animation/EventsTest.java
@@ -22,6 +22,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Tests for the various lifecycle events of Animators. This abstract class is subclassed by
@@ -42,12 +43,15 @@
     protected static final int ANIM_DELAY = 100;
     protected static final int ANIM_MID_DURATION = ANIM_DURATION / 2;
     protected static final int ANIM_MID_DELAY = ANIM_DELAY / 2;
+    protected static final int ANIM_PAUSE_DURATION = ANIM_DELAY;
+    protected static final int ANIM_PAUSE_DELAY = ANIM_DELAY / 2;
     protected static final int FUTURE_RELEASE_DELAY = 50;
+    protected static final int ANIM_FULL_DURATION_SLOP = 100;
 
     private boolean mStarted;  // tracks whether we've received the onAnimationStart() callback
     protected boolean mRunning;  // tracks whether we've started the animator
-    private boolean mCanceled; // trackes whether we've canceled the animator
-    protected Animator.AnimatorListener mFutureListener; // mechanism for delaying the end of the test
+    private boolean mCanceled; // tracks whether we've canceled the animator
+    protected Animator.AnimatorListener mFutureListener; // mechanism for delaying end of the test
     protected FutureWaiter mFuture; // Mechanism for waiting for the UI test to complete
     private Animator.AnimatorListener mListener; // Listener that handles/tests the events
 
@@ -104,6 +108,48 @@
     };
 
     /**
+     * Pauses the given animator. Used to delay pausing until some later time (after the
+     * animator has started playing).
+     */
+    static class Pauser implements Runnable {
+        Animator mAnim;
+        FutureWaiter mFuture;
+        public Pauser(Animator anim, FutureWaiter future) {
+            mAnim = anim;
+            mFuture = future;
+        }
+        @Override
+        public void run() {
+            try {
+                mAnim.pause();
+            } catch (junit.framework.AssertionFailedError e) {
+                mFuture.setException(new RuntimeException(e));
+            }
+        }
+    };
+
+    /**
+     * Resumes the given animator. Used to delay resuming until some later time (after the
+     * animator has paused for some duration).
+     */
+    static class Resumer implements Runnable {
+        Animator mAnim;
+        FutureWaiter mFuture;
+        public Resumer(Animator anim, FutureWaiter future) {
+            mAnim = anim;
+            mFuture = future;
+        }
+        @Override
+        public void run() {
+            try {
+                mAnim.resume();
+            } catch (junit.framework.AssertionFailedError e) {
+                mFuture.setException(new RuntimeException(e));
+            }
+        }
+    };
+
+    /**
      * Releases the given Future object when the listener's end() event is called. Specifically,
      * it releases it after some further delay, to give the test time to do other things right
      * after an animation ends.
@@ -555,4 +601,114 @@
         mFuture.get(getTimeout(),  TimeUnit.MILLISECONDS);
      }
 
+    /**
+     * Verify that pausing and resuming an animator ends within
+     * the appropriate timeout duration.
+     */
+    @MediumTest
+    public void testPauseResume() throws Exception {
+        mFutureListener = new FutureReleaseListener(mFuture);
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Handler handler = new Handler();
+                    mAnimator.addListener(mFutureListener);
+                    mRunning = true;
+                    mAnimator.start();
+                    handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY);
+                    handler.postDelayed(new Resumer(mAnimator, mFuture),
+                            ANIM_PAUSE_DELAY + ANIM_PAUSE_DURATION);
+                } catch (junit.framework.AssertionFailedError e) {
+                    mFuture.setException(new RuntimeException(e));
+                }
+            }
+        });
+        mFuture.get(getTimeout() + ANIM_PAUSE_DURATION, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Verify that pausing and resuming a startDelayed animator ends within
+     * the appropriate timeout duration.
+     */
+    @MediumTest
+    public void testPauseResumeDelayed() throws Exception {
+        mAnimator.setStartDelay(ANIM_DELAY);
+        mFutureListener = new FutureReleaseListener(mFuture);
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Handler handler = new Handler();
+                    mAnimator.addListener(mFutureListener);
+                    mRunning = true;
+                    mAnimator.start();
+                    handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY);
+                    handler.postDelayed(new Resumer(mAnimator, mFuture),
+                            ANIM_PAUSE_DELAY + ANIM_PAUSE_DURATION);
+                } catch (junit.framework.AssertionFailedError e) {
+                    mFuture.setException(new RuntimeException(e));
+                }
+            }
+        });
+        mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP,
+                TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Verify that pausing an animator without resuming it causes a timeout.
+     */
+    @MediumTest
+    public void testPauseTimeout() throws Exception {
+        mFutureListener = new FutureReleaseListener(mFuture);
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Handler handler = new Handler();
+                    mAnimator.addListener(mFutureListener);
+                    mRunning = true;
+                    mAnimator.start();
+                    handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY);
+                } catch (junit.framework.AssertionFailedError e) {
+                    mFuture.setException(new RuntimeException(e));
+                }
+            }
+        });
+        try {
+            mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP,
+                    TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            // Expected behavior, swallow the exception
+        }
+    }
+
+    /**
+     * Verify that pausing a startDelayed animator without resuming it causes a timeout.
+     */
+    @MediumTest
+    public void testPauseTimeoutDelayed() throws Exception {
+        mAnimator.setStartDelay(ANIM_DELAY);
+        mFutureListener = new FutureReleaseListener(mFuture);
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    Handler handler = new Handler();
+                    mAnimator.addListener(mFutureListener);
+                    mRunning = true;
+                    mAnimator.start();
+                    handler.postDelayed(new Pauser(mAnimator, mFuture), ANIM_PAUSE_DELAY);
+                } catch (junit.framework.AssertionFailedError e) {
+                    mFuture.setException(new RuntimeException(e));
+                }
+            }
+        });
+        try {
+            mFuture.get(getTimeout() + ANIM_PAUSE_DURATION + ANIM_FULL_DURATION_SLOP,
+                    TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            // Expected behavior, swallow the exception
+        }
+    }
 }
diff --git a/docs/downloads/design/Android_Design_Downloads_20130814.zip b/docs/downloads/design/Android_Design_Downloads_20130814.zip
new file mode 100644
index 0000000..bd290003
--- /dev/null
+++ b/docs/downloads/design/Android_Design_Downloads_20130814.zip
Binary files differ
diff --git a/docs/html/design/downloads/index.jd b/docs/html/design/downloads/index.jd
index 00f4467..b13ba62 100644
--- a/docs/html/design/downloads/index.jd
+++ b/docs/html/design/downloads/index.jd
@@ -4,8 +4,10 @@
 <div class="layout-content-row">
   <div class="layout-content-col span-9">
 
-<p>Want everything? We've bundled all the downloads available on Android Design into a single ZIP file.
-You can also download individual files listed below.</p>
+<p>Want everything? We've bundled all the downloads available on Android Design, except for the
+  <a href="#roboto">Roboto</a> font family, into a single ZIP file. You can also download
+  individual files listed below.</p>
+
 <p>You may use these materials without restriction in your apps and to develop your apps.</p>
 
   </div>
@@ -13,7 +15,7 @@
 
 <p>
   <a class="download-button" onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'All Design Assets']);"
-    href="{@docRoot}downloads/design/Android_Design_Downloads_20120823.zip">Download All</a>
+    href="{@docRoot}downloads/design/Android_Design_Downloads_20130814.zip">Download All</a>
 </p>
 
   </div>
@@ -83,10 +85,12 @@
 <div class="layout-content-row">
   <div class="layout-content-col span-5">
 
-<h4>Roboto</h4>
+<h4 id="roboto">Roboto</h4>
 <p>Ice Cream Sandwich introduced a new type family named Roboto, created specifically for the
 requirements of UI and high-resolution screens.</p>
-<p><a href="{@docRoot}design/style/typography.html#actionbar">More on Roboto</a></p>
+<p><a href="{@docRoot}design/style/typography.html">More on Roboto</a></p>
+<p><a href="http://www.google.com/fonts/specimen/Roboto" class="external-link">Roboto on Google Fonts</a></p>
+<p><a href="http://www.google.com/fonts/specimen/Roboto+Condensed" class="external-link">Roboto Condensed on Google Fonts</a></p>
 
   </div>
   <div class="layout-content-col span-4">
@@ -98,7 +102,7 @@
 
 <p>
   <a class="download-button"  onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Roboto ZIP']);"
-    href="{@docRoot}downloads/design/Roboto_Hinted_20120823.zip">Roboto</a>
+    href="https://github.com/google/roboto/archive/latest-hinted.zip">Roboto</a>
   <a class="download-button"  onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Roboto Specemin Book']);"
     href="{@docRoot}downloads/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a>
 </p>
diff --git a/docs/html/design/media/typography_alphas.png b/docs/html/design/media/typography_alphas.png
deleted file mode 100644
index 4b53bd0..0000000
--- a/docs/html/design/media/typography_alphas.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/design/media/typography_variants.png b/docs/html/design/media/typography_variants.png
new file mode 100644
index 0000000..9b49b20
--- /dev/null
+++ b/docs/html/design/media/typography_variants.png
Binary files differ
diff --git a/docs/html/design/media/typography_variants@2x.png b/docs/html/design/media/typography_variants@2x.png
new file mode 100644
index 0000000..13e7c4f
--- /dev/null
+++ b/docs/html/design/media/typography_variants@2x.png
Binary files differ
diff --git a/docs/html/design/style/typography.jd b/docs/html/design/style/typography.jd
index 114d13b..0d681ab 100644
--- a/docs/html/design/style/typography.jd
+++ b/docs/html/design/style/typography.jd
@@ -10,17 +10,25 @@
   </div>
   <div class="layout-content-col span-5">
 
+<p>
+  <a class="download-button"  onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Roboto ZIP']);"
+    href="https://github.com/google/roboto/archive/latest-hinted.zip">Download Roboto</a>
+</p>
+
 <p>The Android design language relies on traditional typographic tools such as scale, space, rhythm,
 and alignment with an underlying grid. Successful deployment of these tools is essential to help
 users quickly understand a screen of information. To support such use of typography, Ice Cream
-Sandwich introduced a new type family named Roboto, created specifically for the requirements of UI
-and high-resolution screens. The current TextView framework supports regular, bold, italic, and bold
-italic weights by default.</p>
+Sandwich introduced a new type family named
+<a href="http://www.google.com/fonts/specimen/Roboto" class="external-link">Roboto</a>, created
+specifically for the requirements of UI and high-resolution screens.</p>
 
-    <img src="{@docRoot}design/media/typography_alphas.png">
+<p>The current {@link android.widget.TextView} framework offers Roboto in thin, light, regular and bold
+weights, along with an italic style for each weight. The framework also offers the
+<a href="http://www.google.com/fonts/specimen/Roboto+Condensed" class="external-link">Roboto Condensed</a>
+variant in regular and bold weights, along with an italic style for each weight.</p>
 
-<p><a onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Roboto ZIP (@typography page)']);"
-      href="{@docRoot}downloads/design/Roboto_Hinted_20120823.zip">Download Roboto</a></p>
+    <img src="{@docRoot}design/media/typography_variants@2x.png" width="220">
+
 <p><a onClick="_gaq.push(['_trackEvent', 'Design', 'Download', 'Roboto Specimen Booke (@typography page)']);"
       href="{@docRoot}downloads/design/Roboto_Specimen_Book_20111129.pdf">Specimen Book</a></p>
 
diff --git a/docs/html/google/gcm/adv.jd b/docs/html/google/gcm/adv.jd
index 567b12c..1360624 100644
--- a/docs/html/google/gcm/adv.jd
+++ b/docs/html/google/gcm/adv.jd
@@ -22,12 +22,7 @@
   </ol>
 </li>
 <li><a href="#retry">Automatic Retry Using Exponential Back-Off</a></li>
-<li><a href="#unreg">Unregistration</a>
-  <ol>
-    <li><a href="#unreg-why">Why you should rarely unregister</a></li>
-    <li><a href="#unreg-how">How unregistration works</a></li>
-  </ol>
-</li>
+<li><a href="#unreg">How Unregistration Works</a></li>
 <li><a href="#collapsible">Send-to-Sync vs. Messages with Payload</a>
   <ol>
     <li><a href="#s2s">Send-to-sync messages</a></li>
@@ -36,8 +31,7 @@
     </ol>
 </li>
 <li><a href="#ttl">Setting an Expiration Date for a Message</a> </li>
-<li><a href="#throttling"></a><a href="#multi-senders">Receiving Messages from
-Multiple Senders</a></li>
+<li><a href="#throttling"></a><a href="#multi-senders">Receiving Messages from Multiple Senders</a></li>
 </ol>
 
 </div>
@@ -48,56 +42,17 @@
 
 
 <h2 id="msg-lifetime">Lifetime of a Message</h2>
-<p>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.</p>
-
-<p>In the best-case scenario, if the device is connected to GCM, the screen is on,
-and there are no throttling restrictions (see <a href="#throttling">Throttling</a>),
-the message will be delivered right away.</p>
-
+<p>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.</p>
+<p>In the best-case scenario, if the device is connected to GCM, the screen is on, and there are no throttling restrictions (see <a href="#throttling">Throttling</a>), the message will be delivered right away.</p>
 <p>If the device is connected but idle, the message will still be
-delivered right away unless the <code>delay_while_idle</code> flag is set to true.
-Otherwise, it will be stored in the GCM servers until the device is awake. And
-that's where the <code>collapse_key</code> 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 <a href="#s2s">send-to-sync messages</a>.</p>
+delivered right away unless the <code>delay_while_idle</code> flag is set to true. Otherwise, it will be stored in the GCM servers until the device is awake. And that's where the <code>collapse_key</code> 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.</p>
 
-<p class="note"><strong>Note:</strong> 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.
-<br><br>
-Likewise, there is a limit on how many <code>collapse_key</code>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 <a href="#s2s">Send-to-sync messages</a> for more information.
-</p>
+<p class="note"><strong>Note:</strong> 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.</p>
 
-<p>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 <code>delay_while_idle</code> 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
-<code>time_to_live</code> flag is set.</p>
+<p>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 <code>delay_while_idle</code> 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 <code>time_to_live</code> flag is set.</p>
 
-<p>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 <code>NotRegistered</code> error. See <a href="#unreg">
-How Unregistration Works</a> for more information.</p>
-<p>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.</p>
+<p>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 <code>NotRegistered</code> error. See <a href="#unreg">How Unregistration Works</a> for more information.</p>
+<p>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.</p>
 
 <h2 id="throttling">Throttling</h2>
 <p>To prevent abuse (such as sending a flood of messages to a device) and
@@ -119,112 +74,107 @@
 efficiency reasons.</p>
 
 <h2 id="reg-state">Keeping the Registration State in Sync</h2>
-<p>Whenever the application registers as described in
-<a href="{@docRoot}google/gcm/client.html">Implementing GCM Client</a>,
-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.</p>
-
+<p>Whenever the application receives a <code>com.google.android.c2dm.intent.REGISTRATION</code> intent with a <code>registration_id</code> 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.</p>
 <p>There are also two other scenarios that require special care:</p>
 <ul>
   <li>Application update</li>
   <li>Backup and restore
   </li>
 </ul>
-<p>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.</p>
+<p>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.</p>
 
-<p>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&mdash;thus the application will not get more
-messages).</p>
+<p>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&mdash;thus the application will not get more messages).</p>
 <h3 id="canonical">Canonical IDs</h3>
-<p>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.</p>
-<p>GCM provides a facility called &quot;canonical registration IDs&quot; 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.</p>
-<p>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 <code>registration_id</code> 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.</p>
+<p>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.</p>
+<p>GCM provides a facility called &quot;canonical registration IDs&quot; 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.</p>
+<p>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 <code>registration_id</code> 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.</p>
 
 <h2 id="retry">Automatic Retry Using Exponential Back-Off</h2>
 
-<p>When registration or unregistration fails, the app should retry the failed operation.</p>
-<p>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.
+<p>When the application receives a <code>com.google.android.c2dm.intent.REGISTRATION</code> intent with the <code>error</code> extra set as <code>SERVICE_NOT_AVAILABLE</code>, it should retry the failed operation (register or unregister).</p>
+<p>In the simplest case, if your application just calls <code>register</code> 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 <code>MyIntentService</code> example used above:</p>
+<ol>
+  <li> Create a random token to verify the origin of the retry intent:
 
-<h2 id="unreg">Unregistration</h2>
+<pre class="prettyprint pretty-java">private static final String TOKEN =
+        Long.toBinaryString(new Random().nextLong());
+</pre>
 
-<p>This section explains when you should unregister in GCM and what happens
-when you do.</p>
+  <li> Change the <code>handleRegistration()</code> method so it creates the pending intent when appropriate:</li>
 
-<h3 id="unreg-why">Why you should rarely unregister</h3>
+<pre class="prettyprint pretty-java">...
+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);
+}
+...</pre>
+<p> 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.</p></li>
 
-<p>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.</p>
+  <li> Change the <code>onHandleIntent()</code> method adding an <code>else if</code> case for the retry intent:</li>
 
-<p>In particular, you should never unregister your app as a mechanism for
-logout or for switching between users, for the following reasons:</p>
+<pre class="prettyprint pretty-java">...
+} 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
+    }
+}
+...</pre>
 
-<ul>
-  <li>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&mdash;there's no guarantee either way.</li>
+  <li> Create a new instance of <code>MyReceiver</code> in your activity:</li>
 
-  <li>Unregistration may take up to 5 minutes to propagate.</li>
-  <li>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.</li>
-</ul>
+<pre class="prettyprint pretty-java">private final MyBroadcastReceiver mRetryReceiver = new MyBroadcastReceiver();
+</pre>
 
+  <li>In the activity's <code>onCreate()</code> method, register the new instance to receive the <code>com.example.gcm.intent.RETRY</code> intent:
+    <pre class="prettyprint pretty-java">...
+IntentFilter filter = new IntentFilter(&quot;com.example.gcm.intent.RETRY&quot;);
+filter.addCategory(getPackageName());
+registerReceiver(mRetryReceiver, filter);
+...</pre>
 
-<p>The solution is to manage your own mapping between users, the regID, and
-individual messages:</p>
+<p class="note"><strong>Note:</strong> You must dynamically create a new instance of the broadcast receiver since the one defined by the manifest can only receive intents with the <code>com.google.android.c2dm.permission.SEND</code> permission. The permission <code>com.google.android.c2dm.permission.SEND</code> is a system permission and as such it cannot be granted to a regular application.</p>
 
-<ul>
-  <li>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.</li>
-  <li>The app running on the device should check to ensure that messages it
-receives match the logged in user.</li>
-</ul>
+</li>
 
+  <li>In the activity's <code>onDestroy()</code> method, unregister the broadcast receiver:</li>
 
-<h3 id="unreg-how">How unregistration works</h3>
-
-<p>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:</p>
+<pre class="prettyprint pretty-java">unregisterReceiver(mRetryReceiver);</pre>
+</ol>
+<h2 id="unreg">How Unregistration Works</h2>
+<p>There are two ways to unregister a device from GCM: manually and automatically.</p>
+<p>An Android application can manually unregister itself by issuing a <code>com.google.android.c2dm.intent.UNREGISTER</code> intent, which is useful when the application offers a logoff feature (so it can unregister on logoff and register again on logon). See the <a href="gcm.html#unregistering">Architectural Overview</a> for more discussion of this topic. This is the sequence of events when an application unregisters itself:</p>
+<ol>
+  <li> The application issues a <code>com.google.android.c2dm.intent.UNREGISTER</code> intent, passing the package name as an extra.</li>
+  <li>When the GCM server is done with the unregistration, it sends a <code>com.google.android.c2dm.intent.REGISTRATION</code> intent with the <code>unregistered</code> extra set.</li>
+  <li>The application then must contact the 3rd-party server so it can remove the registration ID.</li>
+  <li>The application should also clear its registration ID.
+  </li>
+</ol>
+<p>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:</p>
 <ol>
   <li>The end user uninstalls the application.</li>
   <li>The 3rd-party server sends a message to GCM server.</li>
   <li>The GCM server sends the message to the device.</li>
-  <li>The GCM client receives the message and queries Package Manager about
-whether there are broadcast receivers configured to receive it, which returns
-<code>false</code>.
+  <li>The GCM client receives the message and queries Package Manager about whether there are broadcast receivers configured to receive it, which returns <code>false</code>. 
 </li>
   <li>The GCM client informs the GCM server that the application was uninstalled.</li>
   <li>The GCM server marks the registration ID for deletion.</li>
@@ -234,16 +184,9 @@
   </li>
 </ol>
 
-<p class ="note"><strong>Note:</strong> The GCM client is the Google Cloud
-Messaging framework present on the device.</p>
+<p class ="note"><strong>Note:</strong> The GCM client is the Google Cloud Messaging framework present on the device.</p>
 
-<p>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
-<code>NotRegistered</code> error, without any further action being required from
-the 3rd-party server (this scenario happens frequently while an application is
-being developed and tested).</p>
+<p>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 <code>NotRegistered</code> error, without any further action being required from the 3rd-party server (this scenario happens frequently while an application is being developed and tested).</p>
 
 <h2 id="collapsible">Send-to-Sync  vs. Messages with Payload</h2>
 
@@ -253,45 +196,17 @@
   <li>By default, it is stored by GCM for 4 weeks.</li>
 </ul>
 
-<p>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 &quot;send-to-sync&quot; (collapsible) message or a &quot;message with
-payload&quot; (non-collapsible message). These concepts are described in more
-detail in the following sections.</p>
+<p>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 &quot;send-to-sync&quot; (collapsible) message or a &quot;message with payload&quot; (non-collapsible message). These concepts are described in more detail in the following sections.</p>
 
 <h3 id="s2s"><strong>Send-to-sync messages</strong></h3>
 
-<p>A send-to-sync (collapsible) message is often a &quot;tickle&quot; 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 &quot;New mail&quot; 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 &quot;New mail&quot; 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. </p>
+<p>A send-to-sync (collapsible) message is often a &quot;tickle&quot; 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 &quot;New mail&quot; 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 &quot;New mail&quot; 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. </p>
 
-<p>The email and sports applications are cases where you would probably use the
-GCM <code>collapse_key</code> parameter. A <em>collapse key</em> 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,
-&quot;New mail,&quot; &quot;Updates available,&quot; and so on</p>
-<p>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.</p>
+<p>The email and sports applications are cases where you would probably use the GCM <code>collapse_key</code> parameter. A <em>collapse key</em> 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, &quot;New mail,&quot; &quot;Updates available,&quot; and so on</p>
+<p>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.</p>
 
 <h3 id="payload">Messages with payload</h3>
-<p>Unlike a send-to-sync message, every &quot;message with payload&quot;
-(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:</p>
+<p>Unlike a send-to-sync message, every &quot;message with payload&quot; (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:</p>
 
 <pre class="prettyprint pretty-json">{
   "registration_id" : "APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx...",
@@ -302,42 +217,19 @@
   },
 }</pre>
 
-<p>A &quot;message with payload&quot; is not simply a &quot;ping&quot; 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 <code>collapse_key</code> parameter. Thus GCM will send each message
-individually. Note that the order of delivery is not guaranteed.</p>
-
-<p>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
-<code>com.google.android.c2dm.intent.RECEIVE</code> intent, with the following
-extras:</p>
+<p>A &quot;message with payload&quot; is not simply a &quot;ping&quot; 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 <code>collapse_key</code> parameter. Thus GCM will send each message individually. Note that the order of delivery is not guaranteed.</p>
+<p>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 <code>com.google.android.c2dm.intent.RECEIVE</code> intent, with the following extras:</p>
 <ul>
-  <li> <code>message_type</code>&mdash;The value is always the string
-&quot;deleted_messages&quot;.</li>
-  <li><code>total_deleted</code>&mdash;The value  is a string with the number of
-deleted messages.</li>
+  <li> <code>message_type</code>&mdash;The value is always the string &quot;deleted_messages&quot;.</li>
+  <li><code>total_deleted</code>&mdash;The value  is a string with the number of deleted messages.</li>
 </ul>
-<p>The application should respond by syncing with the server to recover the
-discarded messages. </p>
+<p>The application should respond by syncing with the server to recover the discarded messages. </p>
 
 <h3 id="which">Which should I use?</h3>
-  <p>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
-<strong>GCM only allows a maximum of 4 different collapse keys to be used by the GCM server
-per device at any given time</strong>. You must not exceed this number, or it could cause
-unpredictable consequences.</p>
+  <p>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.</p>
 
 <h2 dir="ltr" id="ttl">Setting an Expiration Date for a Message</h2>
-<p>The Time to Live (TTL) feature lets  the sender  specify the maximum lifespan
-of a message using the <code>time_to_live</code> 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.</p>
+<p>The Time to Live (TTL) feature lets  the sender  specify the maximum lifespan of a message using the <code>time_to_live</code> 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.</p>
 <p>Here are some possible uses for this feature:</p>
 <ul>
   <li>Video chat incoming calls</li>
@@ -345,29 +237,9 @@
   <li>Calendar events</li>
 </ul>
 <h3 id="bg">Background </h3>
-<p>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
-<code>delay_while_idle</code> flag. Finally, GCM might intentionally delay messages
-to prevent an application from consuming excessive resources and negatively
-impacting battery life.</p>
-
-<p>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.</p>
-
-<p>Another advantage of specifying the expiration date for a message is that GCM
-will never throttle messages with a <code>time_to_live</code> value of 0 seconds.
-In other words, GCM will guarantee best effort for messages that must be delivered
-&quot;now or never.&quot; Keep in mind that a <code>time_to_live</code> 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.</p>
-
+<p>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 <code>delay_while_idle</code> flag. Finally, GCM might intentionally delay messages to prevent an application from consuming excessive resources and negatively impacting battery life.</p>
+<p>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.</p>
+<p>Another advantage of specifying the expiration date for a message is that GCM will never throttle messages with a <code>time_to_live</code> value of 0 seconds. In other words, GCM will guarantee best effort for messages that must be delivered &quot;now or never.&quot; Keep in mind that a <code>time_to_live</code> 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.</p>
 <p>Here is an example of a JSON-formatted request that includes TTL:</p>
 <pre class="prettyprint pretty-json">
 {
@@ -384,23 +256,9 @@
 
 
 <h2 id="multi-senders">Receiving Messages from Multiple Senders</h2>
-
-<p>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.</p>
-
-<p>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.</p>
-<p>This code snippet illustrates this feature. Senders are passed as an intent
-extra in a comma-separated list:</p>
-
+<p>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.</p>
+<p>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.</p>
+<p>This code snippet illustrates this feature. Senders are passed as an intent extra in a comma-separated list:</p>
 <pre class="prettyprint pretty-java">Intent intent = new Intent(GCMConstants.INTENT_TO_GCM_REGISTRATION);
 intent.setPackage(GSF_PACKAGE);
 intent.putExtra(GCMConstants.EXTRA_APPLICATION_PENDING_INTENT,
@@ -411,3 +269,4 @@
  </pre>
 
 <p>Note that there is limit of 100 multiple senders.</p>
+ 
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd
index 244278e..0cadbd2 100644
--- a/docs/html/google/gcm/ccs.jd
+++ b/docs/html/google/gcm/ccs.jd
@@ -1,96 +1,93 @@
-page.title=GCM Cloud Connection Server (XMPP)
+page.title=GCM Cloud Connection Server
 @jd:body
 
 <div id="qv-wrapper">
 <div id="qv">
 
+<h2>Quickview</h2>
+
+<ul>
+<li>Get an introduction to key CCS terms and concepts.</li>
+<li>Learn how to send and receive both upstream and downstream messages in CCS.</li>
+</ul>
+
 
 <h2>In this document</h2>
 
 <ol class="toc">
+  <li><a href="#gcm">CCS vs. GCM HTTP</a> </li>
   <li><a href="#usage">How to Use CCS</a>
-    <ol class="toc">
-      <li><a href="#auth">Authentication</a></li>
-      </ol>
-      </li>
-    <li><a href="#format">Message Format</a>
-      <ol class="toc">
-        <li><a href="#request">Request format</a></li>
-        <li><a href="#response">Response format</a></li>
-      </ol>
-      </li>
-  <li><a href="#upstream">Upstream Messages</a> </li>
-  <li><a href="#flow">Flow Control</a> </li>
-  <li><a href="#implement">Implementing an XMPP-based App Server</a>
-    <ol class="toc">
-      <li><a href="#smack">Java sample using the Smack library</a></li>
-      <li><a href="#python">Python sample</a></li>
+    <ol>
+      <li><a href="#send_msg">Sending Messages</a></li>
+      <li><a href="#format">Message Format</a></li>
+      <li><a href="#msg_examples">Message Examples</a></li>
     </ol>
   </li>
+  <li><a href="#flow">Flow Control</a> </li>
 </ol>
 
 <h2>See Also</h2>
 
 <ol class="toc">
-<li><a href="{@docRoot}google/play-services/gcm/http.html">HTTP</a></li>
 <li><a href="{@docRoot}google/play-services/gcm/gs.html">Getting Started</a></li>
-<li><a href="{@docRoot}google/play-services/gcm/server.html">Implementing GCM Server</a></li>
-<li><a href="{@docRoot}google/play-services/gcm/client.html">Implementing GCM Client</a></li>
-<li><a href="https://services.google.com/fb/forms/gcm/" class="external-link"
-target="_android">CCS and User Notifications Signup Form</a></li>
+<li><a href="https://services.google.com/fb/forms/gcm/" class="external-link" target="_android">CCS and User Notifications Signup Form</a></li>
 </ol>
 
 </div>
 </div>
 
-<p class="note"><strong>Note:</strong> To try out this feature, sign up using
-<a href="https://services.google.com/fb/forms/gcm/">this form</a>.</p>
+<p class="note"><strong>Note:</strong> To try out this feature, sign up using <a href="https://services.google.com/fb/forms/gcm/">this form</a>.</p>
 
-<p>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.</p>
-<p>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:</p>
+<p>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.</p>
+<p>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:</p>
 <ul>
-  <li>The asynchronous nature of XMPP allows you to send more messages with fewer
-resources.</li>
-  <li>Communication is bidirectional&mdash;not only can the server send messages
-to the device, but the device can send messages back to the server.</li>
-<li>You can send messages back using the same connection used for receiving,
-thereby improving battery life.</li>
+  <li>The asynchronous nature of XMPP allows you to send more messages with fewer resources.</li>
+  <li>Communication is bidirectional&mdash;not only can the server send messages to the device, but the device can send messages back to the server.</li>
+<li>You can send messages back using the same connection used for receiving, thereby improving battery life.</li>
 </ul>
 
-<p>The upstream messaging (device-to-cloud) feature of CCS is part of the Google
-Play services platform. Upstream messaging is available through the
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a>
-APIs. For examples, see
-<a href="#implement">Implementing an XMPP-based App Server</a>.</p>
+<p>The upstream messaging (device-to-cloud) feature of CCS is part of the Google Play services platform. Upstream messaging is available through the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> APIs. To use upstream messaging and the new streamlined registration process, you must <a href="{@docRoot}google/play-services/setup.html">set up</a> the Google Play services SDK.</p>
 
-<p class="note"><strong>Note:</strong> See
-<a href="server.html#params">Implementing GCM Server</a> for a list of all the message
-parameters and which connection server(s) supports them.</p>
+<p class="note"><strong>Note:</strong> For an example of an XMPP server, see <a href="server.html#xmpp">GCM Server</a>.
 
+<h2 id="gcm">CCS vs. GCM HTTP</h2>
+
+<p>CCS messaging differs from GCM HTTP messaging in the following ways:</p>
+<ul>
+  <li>Upstream/Downstream messages
+    <ul>
+      <li>GCM HTTP: Downstream only: cloud-to-device. </li>
+      <li>CCS: Upstream and downstream (device-to-cloud, cloud-to-device). </li>
+    </ul>
+  </li>
+  <li>Asynchronous messaging
+    <ul>
+      <li>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.</li>
+      <li>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.</li>
+    </ul>
+  </li>
+
+  <li>JSON
+    <ul>
+      <li>GCM HTTP: JSON messages sent as HTTP POST.</li>
+      <li>CCS: JSON messages encapsulated in XMPP messages.</li>
+    </ul>
+  </li>
+</ul>
+<p>This document describes how to use CCS. For general concepts and information on how to use GCM HTTP, see the <a href="gcm.html">GCM Architectural Overview</a>.</p>
 
 <h2 id="usage">How to Use CCS</h2>
 
-<p>GCM Cloud Connection Server (CCS) is an XMPP endpoint, running on
-{@code http://gcm.googleapis.com} port 5235.</p>
+<p>GCM Cloud Connection Server (CCS) is an XMPP endpoint, running on {@code http://gcm.googleapis.com} port 5235.</p>
 
-<p>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)}.</p>
+<p>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.</p>
 
-<p>CCS requires a SASL PLAIN authentication mechanism using
-{@code &lt;your_GCM_Sender_Id&gt;&#64;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 <a href="gs.html">Getting Started</a>.</p>
+<p>CCS requires a SASL PLAIN authentication mechanism using {@code &lt;your_GCM_Sender_Id&gt;&#64;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 <a href="gs.html">Getting Started</a>.</p>
 
 <p> You can use most XMPP libraries to interact with CCS.</p>
 
-<h3 id="auth">Authentication</h3>
+<h3 id="send_msg">Sending messages</h3>
 
 <p>The following snippets illustrate how to perform authentication in CCS.</p>
 <h4>Client</h4>
@@ -111,13 +108,13 @@
 <h4>Client</h4>
 <pre>&lt;auth mechanism=&quot;PLAIN&quot;
 xmlns=&quot;urn:ietf:params:xml:ns:xmpp-sasl&quot;&gt;MTI2MjAwMzQ3OTMzQHByb2plY3RzLmdjbS5hb
+mRyb2lkLmNvbQAxMjYyMDAzNDc5FzNAcHJvamVjdHMtZ2EtLmFuZHJvaWQuY29tAEFJe
 mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==&lt;/auth&gt;
 </pre>
-
 <h4>Server</h4>
 <pre>&lt;success xmlns=&quot;urn:ietf:params:xml:ns:xmpp-sasl&quot;/&gt;</pre>
 
-<h2 id="format">Message Format</h2>
+<h3 id="format">Message Format</h3>
 <p>CCS uses normal XMPP <code>&lt;message&gt;</code> stanzas. The body of the message must be:
 </p>
 <pre>
@@ -126,42 +123,25 @@
 &lt;/gcm&gt;
 </pre>
 
-<p>The JSON payload for server-to-device is similar to what the GCM http endpoint
-uses, with these exceptions:</p>
+<p>The JSON payload for server-to-device is similar to what the GCM http endpoint uses, with these exceptions:</p>
 <ul>
   <li>There is no support for multiple recipients.</li>
   <li>{@code to} is used instead of {@code registration_ids}.</li>
-  <li>CCS adds the field {@code message_id}, which is required. This ID uniquely
-identifies the message in an XMPP connection. The ACK or NACK from CCS uses the
-{@code message_id} to identify a message sent from 3rd-party app servers to CCS.
-Therefore, it's important that this {@code message_id} not only be unique, but
-always present.</li>
+  <li>CCS adds the field {@code message_id}, which is required. This ID uniquely identifies the message in an XMPP connection. The ACK or NACK from CCS uses the {@code message_id} to identify a message sent from 3rd-party servers to CCS. Therefore, it's important that this {@code message_id} not only be unique, but always present.</li>
 
-<li>For ACK/NACK messages that are special control messages, you also need to
-include a {@code message_type} field in the JSON message. The value can be either
-'ack' or 'nack'. For example:
+  <li>For ACK/NACK messages that are special control messages, you also need to include a {@code message_type} field in the JSON message. For example:
 
-<pre>message_type = ('ack');</pre>
+<pre>message_type = ('ack' OR 'nack');</pre>
   </li>
 </ul>
-<p>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.
+<p>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.
 </p>
-<p>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
-<a href="#flow">Flow Control</a> for details.
+<p>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.
 </p>
 
-<p class="note"><strong>Note:</strong> See
-<a href="server.html#params">Implementing GCM Server</a> for a list of all the message
-parameters and which connection server(s) supports them.</p>
+<h3 id="msg_examples">Message Examples</h3>
 
-<h3 id="request">Request format</h3>
-
-<p>Here is an XMPP stanza containing the JSON message from a 3rd-party app server to CCS:
+<p>Here is an XMPP stanza containing the JSON message from a 3rd-party server to CCS:
 
 </p>
 <pre>&lt;message id=&quot;&quot;&gt;
@@ -180,15 +160,7 @@
 &lt;/message&gt;
 </pre>
 
-<h3 id="response">Response format</h3>
-
-<p>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.</p>
-
-<h4 id="ack">ACK message</h4>
-
-<p>Here is an XMPP stanza containing the ACK/NACK message from CCS to 3rd-party app server:
+<p>Here is an XMPP stanza containing the ACK/NACK message from CCS to 3rd-party server:
 </p>
 <pre>&lt;message id=&quot;&quot;&gt;
   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
@@ -199,138 +171,24 @@
   }
   &lt;/gcm&gt;
 &lt;/message&gt;
-</pre>
 
-<h4 id="nack">NACK message</h4>
-
-<p>A NACK error is a regular XMPP message in which the {@code message_type} status
-message is &quot;nack&quot;. A NACK message contains:</p>
-<ul>
-<li>Nack error code.</li>
-<li>Nack error description.</li>
-</ul>
-
-<p>Below are some examples.</p>
-
-<p>Bad registration:</p>
-<pre>&lt;message&gt;
-  &lt;data:gcm xmlns:data=&quot;google:mobile:data&quot;&gt;
-  {
-    &quot;error&quot;:&quot;BAD_REGISTRATION&quot;,  // error code
-    &quot;message_id&quot;:&quot;msgId1&quot;,
-    &quot;from&quot;:&quot;PA91bHFOtaQGSwupt5l1og&quot;,
-    &quot;message_type&quot;:&quot;nack&quot;
-  }
-  &lt;/data:gcm&gt;
-&lt;/message&gt;</pre>
-
-<p>Invalid "time to live":</p>
-
-<pre>&lt;message&gt;
-  &lt;data:gcm xmlns:data=&quot;google:mobile:data&quot;&gt;
-  {
-     &quot;error&quot;:&quot;InvalidJson : INVALID_TTL : Invalid value (-1) for \&quot;time_to_live\&quot;: must be between 0 and \&quot;2419200\&quot;\n&quot;,
-     &quot;message_id&quot;:&quot;msgId1&quot;,
-     &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
-     &quot;message_type&quot;:&quot;nack&quot;
-  }
-  &lt;/data:gcm&gt;
-&lt;/message&gt;</pre>
-
-<p>JSON type error:</p>
-
-<pre>&lt;message&gt;
-  &lt;data:gcm xmlns:data=&quot;google:mobile:data&quot;&gt;
-  {
-     &quot;error&quot;:&quot;InvalidJson : JSON_TYPE_ERROR : Field \&quot;delay_while_idle\&quot; must be a JSON java.lang.Boolean: not-boolean-user-supplied-value\n&quot;,
-     &quot;message_id&quot;:&quot;msgId1&quot;,
-     &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
-     &quot;message_type&quot;:&quot;nack&quot;
-  }
-  &lt;/data:gcm&gt;
-&lt;/message&gt;</pre>
-
-
-<p>The following table lists some of the more common NACK error codes.</p>
-
-<p class="table-caption" id="table1">
-  <strong>Table 1.</strong> NACK error codes.</p>
-
-<table border="1">
-<tr>
-<th>Error Code</th>
-<th>Description</th>
-</tr>
-<tr>
-<td>{@code BAD_REGISTRATION}</td>
-<td>The device has a registration ID, but it's invalid.</td>
-</tr>
-<tr>
-<td>{@code DEVICE_UNREGISTERED}</td>
-<td>The device is not registered.</td>
-</tr>
-<tr>
-<td>{@code INTERNAL_SERVER_ERROR}</td>
-<td>The server encountered an error while trying to process the request.</td>
-</tr>
-<tr>
-<td>{@code SERVICE_UNAVAILABLE}</td>
-<td>The CCS connection server is temporarily unavailable, try again later
-(using exponential backoff, etc.).</td>
-</tr>
-<tr>
-<td>{@code BAD_ACK}</td>
-<td>The ACK message is improperly formed.</td>
-</tr>
-<tr>
-<td>{@code AUTHENTICATION_FAILED}</td>
-<td>This is a 401 error indicating that there was an error authenticating the sender account.</td>
-</tr>
-<tr>
-<td>{@code INVALID_TTL}</td>
-<td>There was an error in the supplied "time to live" value.</td>
-</tr>
-<tr>
-<td>{@code JSON_TYPE_ERROR}</td>
-<td>There was an error in the supplied JSON data type.</td>
-</tr>
-</table>
-
-<h4 id="stanza">Stanza error</h4>
-
-<p>You can also get a stanza error in certain cases.
-A stanza error contains:</p>
-<ul>
-<li>Stanza error code.</li>
-<li>Stanza error description (free text).</li>
-</ul>
-<p>For example:</p>
-
-<pre>&lt;message id=&quot;3&quot; type=&quot;error&quot; to=&quot;123456789@gcm.googleapis.com/ABC&quot;&gt;
+&lt;message id=&quot;&quot;&gt;
   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
-     {&quot;random&quot;: &quot;text&quot;}
+  {
+      &quot;from&quot;:&quot;REGID&quot;,
+      &quot;message_id&quot;:&quot;m-1366082849205&quot;
+      &quot;error&quot;: ERROR_CODE,
+      &quot;message_type&quot;:&quot;nack&quot;
+  }
   &lt;/gcm&gt;
-  &lt;error code=&quot;400&quot; type=&quot;modify&quot;&gt;
-    &lt;bad-request xmlns=&quot;urn:ietf:params:xml:ns:xmpp-stanzas&quot;/&gt;
-    &lt;text xmlns=&quot;urn:ietf:params:xml:ns:xmpp-stanzas&quot;&gt;
-      InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n
-    &lt;/text&gt;
-  &lt;/error&gt;
 &lt;/message&gt;
 </pre>
 
+<h4>Upstream Messages</h4>
 
-<h2 id="upstream">Upstream Messages</h2>
+<p>Using CCS and the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">GoogleCloudMessaging</a> API, you can send messages from a user's device to the cloud.</p>
 
-<p>Using CCS and the
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a>
-API, you can send messages from a user's device to the cloud.</p>
-
-<p>Here is how you send an upstream message using the
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a>
-API. For a complete example, see <a href="client.html">Implementing GCM Client</a>:</p>
+<p>Here is how you send an upstream message using the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">GoogleCloudMessaging</a> API. For a complete example, see <a href="gs.html#gs_example">Getting Started</a>:</p>
 
 <pre>GoogleCloudMessaging gcm = GoogleCloudMessaging.get(context);
 String GCM_SENDER_ID = "Your-Sender-ID";
@@ -340,15 +198,12 @@
 // 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 + "&#64;gcm.googleapis.com", id, ttl, data);
 </pre>
 
-<p>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:</p>
+<p>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:</p>
 
 <pre>&lt;message id=&quot;&quot;&gt;
   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
@@ -364,8 +219,7 @@
   &lt;/gcm&gt;
 &lt;/message&gt;</pre>
 
-<p>Here is the format of the ACK expected by CCS from 3rd-party app servers in
-response to the above message:</p>
+<p>Here is the format of the ACK expected by CCS from 3rd-party servers in response to the above message:</p>
 
 <pre>&lt;message id=&quot;&quot;&gt;
   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
@@ -377,478 +231,13 @@
   &lt;/gcm&gt;
 &lt;/message&gt;</pre>
 
+
 <h2 id="flow">Flow Control</h2>
 
-<p>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:</p>
+<p>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.</p>
 
-<img src="{@docRoot}images/gcm/CCS-ack.png">
+<p>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.</p>
 
-<p class="img-caption">
-  <strong>Figure 1.</strong> Message/ack flow.
+<p>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.
 </p>
 
-<p>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.</p>
-
-<p>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.
-</p>
-
-<h2 id="implement">Implementing an XMPP-based App Server</h2>
-
-<p>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 <a href="client.html">
-Implementing GCM Client</a>.</a>
-
-<h3 id="smack">Java sample using the Smack library</h3>
-
-<p>Here is a sample app server written in Java, using the
-<a href="http://www.igniterealtime.org/projects/smack/">Smack</a> library.</p>
-
-<pre>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.
- *
- * &lt;p&gt;For illustration purposes only.
- */
-public class SmackCcsClient {
-
-  Logger logger = Logger.getLogger(&quot;SmackCcsClient&quot;);
-
-  public static final String GCM_SERVER = &quot;gcm.googleapis.com&quot;;
-  public static final int GCM_PORT = 5235;
-
-  public static final String GCM_ELEMENT_NAME = &quot;gcm&quot;;
-  public static final String GCM_NAMESPACE = &quot;google:mobile:data&quot;;
-
-  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;
-    }
-
-    &#64;Override
-    public String toXML() {
-      return String.format(&quot;&lt;%s xmlns=\&quot;%s\&quot;&gt;%s&lt;/%s&gt;&quot;, GCM_ELEMENT_NAME,
-          GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
-    }
-
-    &#64;SuppressWarnings(&quot;unused&quot;)
-    public Packet toPacket() {
-      return new Message() {
-        // Must override toXML() because it includes a &lt;body&gt;
-        &#64;Override
-        public String toXML() {
-
-          StringBuilder buf = new StringBuilder();
-          buf.append(&quot;&lt;message&quot;);
-          if (getXmlns() != null) {
-            buf.append(&quot; xmlns=\&quot;&quot;).append(getXmlns()).append(&quot;\&quot;&quot;);
-          }
-          if (getLanguage() != null) {
-            buf.append(&quot; xml:lang=\&quot;&quot;).append(getLanguage()).append(&quot;\&quot;&quot;);
-          }
-          if (getPacketID() != null) {
-            buf.append(&quot; id=\&quot;&quot;).append(getPacketID()).append(&quot;\&quot;&quot;);
-          }
-          if (getTo() != null) {
-            buf.append(&quot; to=\&quot;&quot;).append(StringUtils.escapeForXML(getTo())).append(&quot;\&quot;&quot;);
-          }
-          if (getFrom() != null) {
-            buf.append(&quot; from=\&quot;&quot;).append(StringUtils.escapeForXML(getFrom())).append(&quot;\&quot;&quot;);
-          }
-          buf.append(&quot;&gt;&quot;);
-          buf.append(GcmPacketExtension.this.toXML());
-          buf.append(&quot;&lt;/message&gt;&quot;);
-          return buf.toString();
-        }
-      };
-    }
-  }
-
-  public SmackCcsClient() {
-    // Add GcmPacketExtension
-    ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
-        GCM_NAMESPACE, new PacketExtensionProvider() {
-
-      &#64;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.
-   *
-   * &lt;p&gt;Note:
-   * This is generated by a pseudo random number generator for illustration purpose,
-   * and is not guaranteed to be unique.
-   *
-   */
-  public String getRandomMessageId() {
-    return &quot;m-&quot; + 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.
-   *
-   * &lt;p&gt;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&lt;String, Object&gt; jsonObject) {
-    String from = jsonObject.get(&quot;from&quot;).toString();
-
-    // PackageName of the application that sent this message.
-    String category = jsonObject.get(&quot;category&quot;).toString();
-
-    // Use the packageName as the collapseKey in the echo packet
-    String collapseKey = &quot;echo:CollapseKey&quot;;
-    &#64;SuppressWarnings(&quot;unchecked&quot;)
-    Map&lt;String, String&gt; payload = (Map&lt;String, String&gt;) jsonObject.get(&quot;data&quot;);
-    payload.put(&quot;ECHO&quot;, &quot;Application: &quot; + category);
-
-    // Send an ECHO response back
-    String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false);
-    send(echo);
-  }
-
-  /**
-   * Handles an ACK.
-   *
-   * &lt;p&gt;By default, it only logs a {@code INFO} message, but subclasses could override it to
-   * properly handle ACKS.
-   */
-  public void handleAckReceipt(Map&lt;String, Object&gt; jsonObject) {
-    String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-    String from = jsonObject.get(&quot;from&quot;).toString();
-    logger.log(Level.INFO, &quot;handleAckReceipt() from: &quot; + from + &quot;, messageId: &quot; + messageId);
-  }
-
-  /**
-   * Handles a NACK.
-   *
-   * &lt;p&gt;By default, it only logs a {@code INFO} message, but subclasses could override it to
-   * properly handle NACKS.
-   */
-  public void handleNackReceipt(Map&lt;String, Object&gt; jsonObject) {
-    String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-    String from = jsonObject.get(&quot;from&quot;).toString();
-    logger.log(Level.INFO, &quot;handleNackReceipt() from: &quot; + from + &quot;, messageId: &quot; + messageId);
-  }
-
-  /**
-   * Creates a JSON encoded GCM message.
-   *
-   * &#64;param to RegistrationId of the target device (Required).
-   * &#64;param messageId Unique messageId for which CCS will send an &quot;ack/nack&quot; (Required).
-   * &#64;param payload Message content intended for the application. (Optional).
-   * &#64;param collapseKey GCM collapse_key parameter (Optional).
-   * &#64;param timeToLive GCM time_to_live parameter (Optional).
-   * &#64;param delayWhileIdle GCM delay_while_idle parameter (Optional).
-   * &#64;return JSON encoded GCM message.
-   */
-  public static String createJsonMessage(String to, String messageId, Map&lt;String, String&gt; payload,
-      String collapseKey, Long timeToLive, Boolean delayWhileIdle) {
-    Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
-    message.put(&quot;to&quot;, to);
-    if (collapseKey != null) {
-      message.put(&quot;collapse_key&quot;, collapseKey);
-    }
-    if (timeToLive != null) {
-      message.put(&quot;time_to_live&quot;, timeToLive);
-    }
-    if (delayWhileIdle != null &amp;&amp; delayWhileIdle) {
-      message.put(&quot;delay_while_idle&quot;, true);
-    }
-    message.put(&quot;message_id&quot;, messageId);
-    message.put(&quot;data&quot;, payload);
-    return JSONValue.toJSONString(message);
-  }
-
-  /**
-   * Creates a JSON encoded ACK message for an upstream message received from an application.
-   *
-   * &#64;param to RegistrationId of the device who sent the upstream message.
-   * &#64;param messageId messageId of the upstream message to be acknowledged to CCS.
-   * &#64;return JSON encoded ack.
-   */
-  public static String createJsonAck(String to, String messageId) {
-    Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
-    message.put(&quot;message_type&quot;, &quot;ack&quot;);
-    message.put(&quot;to&quot;, to);
-    message.put(&quot;message_id&quot;, messageId);
-    return JSONValue.toJSONString(message);
-  }
-
-  /**
-   * Connects to GCM Cloud Connection Server using the supplied credentials.
-   *
-   * &#64;param username GCM_SENDER_ID&#64;gcm.googleapis.com
-   * &#64;param password API Key
-   * &#64;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() {
-
-      &#64;Override
-      public void reconnectionSuccessful() {
-        logger.info(&quot;Reconnecting..&quot;);
-      }
-
-      &#64;Override
-      public void reconnectionFailed(Exception e) {
-        logger.log(Level.INFO, &quot;Reconnection failed.. &quot;, e);
-      }
-
-      &#64;Override
-      public void reconnectingIn(int seconds) {
-        logger.log(Level.INFO, &quot;Reconnecting in %d secs&quot;, seconds);
-      }
-
-      &#64;Override
-      public void connectionClosedOnError(Exception e) {
-        logger.log(Level.INFO, &quot;Connection closed on error.&quot;);
-      }
-
-      &#64;Override
-      public void connectionClosed() {
-        logger.info(&quot;Connection closed.&quot;);
-      }
-    });
-
-    // Handle incoming packets
-    connection.addPacketListener(new PacketListener() {
-
-      &#64;Override
-      public void processPacket(Packet packet) {
-        logger.log(Level.INFO, &quot;Received: &quot; + packet.toXML());
-        Message incomingMessage = (Message) packet;
-        GcmPacketExtension gcmPacket =
-            (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE);
-        String json = gcmPacket.getJson();
-        try {
-          &#64;SuppressWarnings(&quot;unchecked&quot;)
-          Map&lt;String, Object&gt; jsonObject =
-              (Map&lt;String, Object&gt;) JSONValue.parseWithException(json);
-
-          // present for &quot;ack&quot;/&quot;nack&quot;, null otherwise
-          Object messageType = jsonObject.get(&quot;message_type&quot;);
-
-          if (messageType == null) {
-            // Normal upstream data message
-            handleIncomingDataMessage(jsonObject);
-
-            // Send ACK to CCS
-            String messageId = jsonObject.get(&quot;message_id&quot;).toString();
-            String from = jsonObject.get(&quot;from&quot;).toString();
-            String ack = createJsonAck(from, messageId);
-            send(ack);
-          } else if (&quot;ack&quot;.equals(messageType.toString())) {
-            // Process Ack
-            handleAckReceipt(jsonObject);
-          } else if (&quot;nack&quot;.equals(messageType.toString())) {
-            // Process Nack
-            handleNackReceipt(jsonObject);
-          } else {
-            logger.log(Level.WARNING, &quot;Unrecognized message type (%s)&quot;,
-                messageType.toString());
-          }
-        } catch (ParseException e) {
-          logger.log(Level.SEVERE, &quot;Error parsing JSON &quot; + json, e);
-        } catch (Exception e) {
-          logger.log(Level.SEVERE, &quot;Couldn't send echo.&quot;, e);
-        }
-      }
-    }, new PacketTypeFilter(Message.class));
-
-
-    // Log all outgoing packets
-    connection.addPacketInterceptor(new PacketInterceptor() {
-      &#64;Override
-      public void interceptPacket(Packet packet) {
-        logger.log(Level.INFO, &quot;Sent: {0}&quot;,  packet.toXML());
-      }
-    }, new PacketTypeFilter(Message.class));
-
-    connection.login(username, password);
-  }
-
-  public static void main(String [] args) {
-    final String userName = &quot;Your GCM Sender Id&quot; + &quot;&#64;gcm.googleapis.com&quot;;
-    final String password = &quot;API Key&quot;;
-
-    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 = &quot;RegistrationIdOfTheTargetDevice&quot;;
-    String messageId = ccsClient.getRandomMessageId();
-    Map&lt;String, String&gt; payload = new HashMap&lt;String, String&gt;();
-    payload.put(&quot;Hello&quot;, &quot;World&quot;);
-    payload.put(&quot;CCS&quot;, &quot;Dummy Message&quot;);
-    payload.put(&quot;EmbeddedMessageId&quot;, messageId);
-    String collapseKey = &quot;sample&quot;;
-    Long timeToLive = 10000L;
-    Boolean delayWhileIdle = true;
-    ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey,
-        timeToLive, delayWhileIdle));
-  }
-}</pre>
-<h3 id="python">Python sample</h3>
-
-<p>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.</p>
-
-<pre>
-#!/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 = (&quot;&lt;message&gt;&lt;gcm xmlns='google:mobile:data'&gt;{1}&lt;/gcm&gt;&lt;/message&gt;&quot;)
-  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 &gt; 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()</pre>
diff --git a/docs/html/google/gcm/client.jd b/docs/html/google/gcm/client.jd
index df357a2..7604932 100644
--- a/docs/html/google/gcm/client.jd
+++ b/docs/html/google/gcm/client.jd
@@ -1,663 +1,24 @@
-page.title=Implementing GCM Client
+page.title=GCM Client
 page.tags="cloud","push","messaging"
 @jd:body
 
 <div id="qv-wrapper">
 <div id="qv">
 
-
-<h2>In this document</h2>
-
-<ol class="toc">
-<li><a href="#play-services">Set Up Google Play Services</a></li>
-<li><a href="#manifest">Edit Your Application's Manifest</a></li>
-<li><a href="#app">Write Your Application</a>
-  <ol class="toc">
-    <li><a href="#sample-play">Check for Google Play Services APK</a></li>
-    <li><a href="#sample-register">Register for GCM</a></li>
-    <li><a href="#sample-send">Send a message</a></li>
-    <li><a href="#sample-receive">Receive a message</a></li>
-  </ol>
-  <li><a href="#run">Running the Sample</a></li>
-  <li><a href="#stats">Viewing Statistics</a></li>
-</li>
-
-</ol>
-
 <h2>See Also</h2>
 
 <ol class="toc">
 <li><a href="gs.html">Getting Started</a></li>
-<li><a href="server.html">Implementing GCM Server</a></li>
+<li><a href="server.html">GCM Server</a></li>
 </ol>
 
 </div>
 </div>
 
-<p>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
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a> APIs.
-The client helper library that was offered in previous versions of GCM still works,
-but it has been superseded by the more efficient
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a> APIs.</p>
+<p>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 <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> APIs. The client helper library that was offered in previous versions of GCM still works, but it has been superseded by the more efficient <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> APIs.</p>
 
-<p>A full GCM implementation requires both a client implementation and a server
-implementation. For more
-information about implementing the server side, see <a href="server.html">
-Implementing GCM Server</a>.</p>
+<p>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 <a href="gs.html">Getting Started</a>. </p>
 
-<p>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.
-</p>
-
-<h2 id="play-services">Step 1: Set Up Google Play Services</h2>
-
-<p>To write your client application, use the
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a> API.
-To use this API, you must set up your project to use the Google Play services SDK,
-as described in <a href="/google/play-services/setup.html">Setup Google Play
-Services SDK</a>.</p>
-
-<p class="note"><strong>Caution:</strong> When you add the Play Services library to
-your project, be sure to add it <em>with resources</em>, as described in
-<a href="{@docRoot}google/play-services/setup.html#Setup">
-Setup Google Play Services SDK</a>. The key point is that you must
-<em>reference</em> the library&mdash;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:</p>
-
-<pre>dependencies {
-   compile: "com.google.android.gms:play-services:3.1.+"
-}
-</pre>
-
-
-<h2 id="manifest">Step 2: Edit Your Application's Manifest</h2>
-
-<p>Add the following to your application's manifest:</p>
-<ul>
-  <li>The <code>com.google.android.c2dm.permission.RECEIVE</code> permission so
-the Android application can register and receive messages.</li>
-  <li>The <code>android.permission.INTERNET</code> permission so the Android
-application can send the registration ID to the 3rd party server.</li>
-  <li>The <code>android.permission.GET_ACCOUNTS</code> permission as GCM requires
-a Google account (necessary only if if the device is running a version lower than
-Android 4.0.4)</li>
-  <li>The <code>android.permission.WAKE_LOCK</code> permission so the application
-can keep the processor from sleeping when a message is received. Optional&mdash;use
-only if the app wants to keep the device from sleeping.</li>
-  <li>An <code>applicationPackage + &quot;.permission.C2D_MESSAGE&quot;</code>
-permission to prevent other Android applications from registering and receiving
-the Android application's messages. The permission name must exactly match this
-pattern&mdash;otherwise the Android application will not receive the messages.</li>
-   <li>A receiver for <code>com.google.android.c2dm.intent.RECEIVE</code>, with
-the category set
-as <code>applicationPackage</code>. The receiver should require the
-<code>com.google.android.c2dm.SEND</code> 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
-<a href="{@docRoot}reference/android/os/PowerManager.html#PARTIAL_WAKE_LOCK">
-partial wake lock</a> for your app.</li>
-
-<li>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&mdash;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}.
-</li>
-  <li>If the GCM feature is critical to the Android application's function, be sure to
-set <code>android:minSdkVersion=&quot;8&quot;</code> or higher in the manifest. This
-ensures that the Android application cannot be installed in an environment in which it
-could not run properly. </li>
-</ul>
-
-<p>Here are excerpts from a sample manifest that supports GCM:</p>
-
-<pre class="prettyprint pretty-xml">
-&lt;manifest package="com.example.gcm" ...&gt;
-
-    &lt;uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/&gt;
-    &lt;uses-permission android:name="android.permission.INTERNET" /&gt;
-    &lt;uses-permission android:name="android.permission.GET_ACCOUNTS" /&gt;
-    &lt;uses-permission android:name="android.permission.WAKE_LOCK" /&gt;
-    &lt;uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /&gt;
-
-    &lt;permission android:name="com.example.gcm.permission.C2D_MESSAGE"
-        android:protectionLevel="signature" /&gt;
-    &lt;uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" /&gt;
-
-    &lt;application ...&gt;
-        &lt;receiver
-            android:name=".GcmBroadcastReceiver"
-            android:permission="com.google.android.c2dm.permission.SEND" &gt;
-            &lt;intent-filter&gt;
-                &lt;action android:name="com.google.android.c2dm.intent.RECEIVE" /&gt;
-                &lt;category android:name="com.example.gcm" /&gt;
-            &lt;/intent-filter&gt;
-        &lt;/receiver&gt;
-        &lt;service android:name=".GcmIntentService" /&gt;
-    &lt;/application&gt;
-
-&lt;/manifest&gt;
-</pre>
-
-<h2 id="app"> Step 3: Write Your Application</h2>
-
-<p>Finally, write your application. This section features a sample client
-application that illustrates how to use the
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a> 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
-<a href="http://code.google.com/p/gcm">open source site</a>.</p>
-
-<p>Note the following:</p>
-
-<ul>
-  <li>Among other things, the sample illustrates registration and upstream
-(device-to-cloud) messaging. Upstream messaging only applies to apps that are running against a
-<a href="ccs.html">CCS</a> (XMPP) server; HTTP-based servers don't support upstream messaging.</li>
-  <li>The <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-  {@code GoogleCloudMessaging}</a>
-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
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a>
-registration APIs, regardless of your underlying server.</li>
-</ul>
-
-<h3 id="sample-play">Check for Google Play Services APK</h3>
-
-<p>As described in <a href="{@docRoot}google/play-services/setup.html">
-Setup Google Play Services SDK</a>, 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:</p>
-
-<pre>private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
-...
-&#64;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.
-&#64;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;
-}</pre>
-
-<h3 id="sample-register">Register for GCM</h3>
-<p>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:</p>
-
-<pre>/**
- * 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;
-
-    &#64;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 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.");
-        }
-    }
-...
-}</pre>
-
-<p>The app calls {@code getRegistrationId()} to see whether there is an existing
-registration ID stored in shared preferences:</p>
-
-<pre>/**
- * Gets the current registration ID for application on GCM service.
- * &lt;p&gt;
- * If result is empty, the app needs to register.
- *
- * &#64;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;
-}
-...
-/**
- * &#64;return Application's {&#64;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);
-}</pre>
-
-<p>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:</p>
-
-<pre>/**
- * &#64;return Application's version code from the {&#64;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);
-    }
-}</pre>
-
-
-<p>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:</p>
-
-<pre>
-/**
- * Registers the application with GCM servers asynchronously.
- * &lt;p&gt;
- * Stores the registration ID and app versionCode in the application's
- * shared preferences.
- */
-private void registerInBackground() {
-    new AsyncTask<Void, Void, String>() {
-        &#64;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;
-        }
-
-        &#64;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.
-    }
-}</pre>
-
-<p>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:</p>
-
-<pre>/**
- * Stores the registration ID and app versionCode in the application's
- * {&#64;code SharedPreferences}.
- *
- * &#64;param context application's context.
- * &#64;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();
-}</pre>
-
-<h3 id="sample-send">Send a message</h3>
-<p>When the user clicks the app's <strong>Send</strong> button, the app sends an
-upstream message using the
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a> APIs. In order to receive the upstream message,
-your server should be connected to CCS. You can use one of the demo servers in
-<a href="ccs.html#implement">Implementing an XMPP-based App Server</a> to run the sample and connect
-to CCS.</p>
-
-<pre>public void onClick(final View view) {
-    if (view == findViewById(R.id.send)) {
-        new AsyncTask<Void, Void, String>() {
-            &#64;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;
-            }
-
-            &#64;Override
-            protected void onPostExecute(String msg) {
-                mDisplay.append(msg + "\n");
-            }
-        }.execute(null, null, null);
-    } else if (view == findViewById(R.id.clear)) {
-        mDisplay.setText("");
-    }
-}</pre>
-
-<h3 id="sample-receive">Receive a message</h3>
-
-<p>As described above in <a href="#manifest">Step 2</a>, the app includes a
-{@link android.support.v4.content.WakefulBroadcastReceiver} for the <code>com.google.android.c2dm.intent.RECEIVE</code>
-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.</p>
-<p>A {@link android.support.v4.content.WakefulBroadcastReceiver} is a special type of
-broadcast receiver that takes care of
-creating and managing a
-<a href="{@docRoot}reference/android/os/PowerManager.html#PARTIAL_WAKE_LOCK">
-partial wake lock</a> 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.</p>
-
-<p class="note"><strong>Note:</strong> 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.</p>
-
-<p>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:</p>
-
-
-<pre>public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
-    &#64;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);
-    }
-}</pre>
-
-<p>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}.
-</p>
-
-<p>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&mdash;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.</p>
-
-<pre>
-public class GcmIntentService extends IntentService {
-    public static final int NOTIFICATION_ID = 1;
-    private NotificationManager mNotificationManager;
-    NotificationCompat.Builder builder;
-
-    public GcmIntentService() {
-        super("GcmIntentService");
-    }
-
-    &#64;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());
-    }
-}</pre>
-
-<h2 id="run">Running the Sample</h2>
-
-<p>To run the sample:</p>
-
-<ol>
-  <li>Follow the instructions in <a href="gs.html">Getting Started</a> to get your sender ID and
-  API key.</li>
-  <li>Implement your client app, as described in this document. You can find the complete source
-  code for the client app at the <a href="http://code.google.com/p/gcm">open source site</a>.</li>
-  <li>Run one of the demo servers (Java or Python) provided in
-<a href="ccs.html#implement">Implementing an XMPP-based App Server</a>. Whichever demo server you
- choose, don't forget to edit its code before running it to supply
-your sender ID and API key.
-</li>
-
-</ol>
-
-<h2 id="stats">Viewing Statistics</h2>
-
-<p>To view  statistics and any error messages for your GCM applications:</p>
-<ol>
-  <li> Go to the <code><a href="http://play.google.com/apps/publish">Developer Console</a></code>.</li>
-  <li>Login with your developer account.
-  <p>You will see a page that has a list of all of your apps.</p></li>
-  <li> Click on the &quot;statistics&quot; link next to the app for which you
-want to view GCM stats.
-  <p>Now you are on the statistics page.</p> </li>
-  <li>Go to the drop-down menu and select the GCM metric you want to view.
-  </li>
-</ol>
-<p class="note"><strong>Note:</strong> Stats on the Google API Console are not
-enabled for GCM. You must use the <a href="http://play.google.com/apps/publish">Developer Console</a>.</p>
+<p>
 
 
diff --git a/docs/html/google/gcm/gcm.jd b/docs/html/google/gcm/gcm.jd
index 3c80b5f..ceb82b0 100644
--- a/docs/html/google/gcm/gcm.jd
+++ b/docs/html/google/gcm/gcm.jd
@@ -1,23 +1,48 @@
-page.title=Overview
+page.title=GCM Architectural Overview
 @jd:body
 
 <div id="qv-wrapper">
 <div id="qv">
 
+<h2>Quickview</h2>
+
+<ul>
+<li>Get an introduction to key GCM terms and concepts.</li>
+<li>Learn the basic features of a GCM application.</li>
+<li>Understand the role of the 3rd-party application server, and how to send messages and process results.</li>
+</ul>
+
+
 <h2>In this document</h2>
 
 <ol class="toc">
-  <li><a href="#key">Key Concepts</a></li>
-  <li><a href="#arch">Architectural Overview</a></li>
-  <li><a href="#lifecycle">Lifecycle Flow</a>
-    <ol class="toc">
-      <li><a href="#register">Enable GCM</a></li>
-      <li><a href="#push-process">Send a message</a></li>
-      <li><a href="#receiving">Receive a message</a></li>
+  <li><a href="#intro">Introduction</a> </li>
+  <li><a href="#arch">Architectural Overview</a>
+    <ol>
+      <li><a href="#lifecycle">Lifecycle Flow</a></li>
+        <ol>
+          <li><a href="#register">Enable GCM</a></li>
+          <li><a href="#push-process">Send a message</a></li>
+          <li><a href="#receiving">Receive a message</a></li>
+        </ol>
+      <li><a href="#user">What Does the User See?</a></li>
     </ol>
   </li>
+  <li><a href="#server">Role of  the 3rd-party Application Server</a>
+    <ol class="toc">
+      <li><a href="#send-msg">Sending Messages</a>
+        <ol>
+          <li><a href="#request">Request format</a></li>
+          <li><a href="#response">Response format</a></li>
+        </ol>
+      </li>
+    </ol>
+    <li><a href="#stats">Viewing Statistics</a>
+  </li>
 </ol>
 
+
+
 </div>
 </div>
 
@@ -25,15 +50,24 @@
 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 "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
+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
 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.</p>
+
+<p>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
+<a href="ccs.html">GCM Cloud Connection Server</a>.
+  
   
 <p class="note"> To jump right into using GCM with your Android
-  applications, see <a href="gs.html">Getting Started</a>.</p>
+  applications, see the instructions in <a href="gs.html">Getting Started</a>.</p>
+
+
+<h2 id="intro">Introduction</h2>
 
 <p>Here are the primary characteristics of Google Cloud 
 Messaging (GCM):</p>
@@ -41,11 +75,9 @@
 <ul>
   <li>It allows 3rd-party application servers to send messages to
 their Android applications.</li>
-  <li>Using the <a href="ccs.html">GCM Cloud Connection Server</a>, you can receive
-upstream messages from the user's device.</li>
+  <li>Using the <a href="ccs.html">GCM Cloud Connection Server</a>, you can receive upstream messages from the user's device.</li>
   <li>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.</li>
   <li>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
@@ -53,67 +85,57 @@
 application might post a notification, display a custom user interface, or 
 silently sync data.</li>
   <li>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.</li>
-  <li>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.</li>
+  <li>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.</li>
 </ul>
-
-<h2 id="key">Key Concepts</h2>
-
+<h2 id="arch">Architectural Overview</h2>
+<p>This section gives an overview of how GCM works. </p>
 <p>This table summarizes the key terms and concepts involved in GCM. It is
 divided into these categories:</p>
 <ul>
-  <li><strong>Components</strong> &mdash; The entities that play a primary role in
+  <li><strong>Components</strong> &mdash; The physical entities that play a role in
 GCM.</li>
   <li><strong>Credentials</strong> &mdash; 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.</li>
 </ul>
 
-<p class="table-caption" id="table1">
-  <strong>Table 1.</strong> GCM components and credentials.</p>
-
 <table>
   <tr>
     <th colspan="2">Components</th>
   </tr>
   <tr>
-    <td width="165"><strong>Client App</strong></td>
-    <td width="1176">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.</td>
+    <td width="165"><strong>Mobile Device</strong></td>
+    <td width="1176">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.</td>
   </tr>
   <tr>
     <td><strong>3rd-party Application Server</strong></td>
-    <td>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.</td>
+    <td>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.</td>
   </tr>
   <tr>
-    <td><strong>GCM Connection Servers</strong></td>
-    <td>The Google-provided servers involved in taking messages from the 3rd-party
+    <td><strong>GCM Servers</strong></td>
+    <td>The Google servers involved in taking messages from the 3rd-party
 application server and sending them to the device. </td>
   </tr>
   <tr>
-    <th colspan="2">Credentials</th>
+    <th colspan="2"><strong>Credentials</strong></th>
   </tr>
   <tr>
     <td><strong>Sender ID</strong></td>
-    <td>A project number you acquire from the API console, as described in
-<a href="gs.html#create-proj">Getting Started</a>. The sender
-ID is used in the <a href="#register">registration process</a> to identify a
-3rd-party application server that is permitted to send messages to the device.</td>
+    <td>A project number you acquire from the API console, as described in <a href="gs.html#create-proj">Getting Started</a>. The sender
+ID is used in the <a href="#registering">registration process</a> to identify an
+Android application that is permitted to send messages to the device.</td>
   </tr>
   <tr>
     <td><strong>Application ID</strong></td>
     <td>The Android application that is registering to receive messages. The Android application
-is identified by the package name from the <a href="client.html#manifest">manifest</a>.
+is identified by the package name from the <a href="#manifest">manifest</a>.
 This  ensures that the messages are targeted to the correct Android application.</td>
   </tr>
   <tr>
@@ -136,8 +158,7 @@
   </tr>
   <tr>
     <td><strong>Google User Account</strong></td>
-    <td>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.</td>
+    <td>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.</td>
   </tr>
   <tr>
     <td><strong>Sender Auth Token</strong></td>
@@ -146,46 +167,25 @@
 The API key is included in the header of POST requests  that send messages.</td>
   </tr>
 
+  <tr>
+    <td><strong>Notification Key</strong></td>
+    <td>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 <a href="notifications.html">User Notifications</a>.</td>
+  </tr>
+
+<tr>
+    <td><strong>Notification Key Name</strong></td>
+    <td>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 <a href="notifications.html">User Notifications</a>.</td>
+  </tr>
+
 </table>
 
-<h2 id="arch">Architectural Overview</h2>
-
-<p>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:</p>
-
-<img src="{@docRoot}images/gcm/GCM-arch.png">
-
-<p class="img-caption">
-  <strong>Figure 1.</strong> GCM Architecture.
-</p>
-
-<p>This is how these components interact:</p>
-<ul>
-  <li>Google-provided <strong>GCM Connection Servers</strong> take messages from
-a 3rd-party application server and send these messages to a
-GCM-enabled Android application (the &quot;client app&quot;) running on a device.
-Currently Google provides connection servers for <a href="http.html">HTTP</a>
-and <a href="ccs.html">XMPP</a>.</li>
-  <li>The <strong>3rd-Party Application Server</strong> 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 <a href="server.html">Implementing GCM Server</a>.</li>
-  <li>The <strong>Client App</strong> 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 <a href="ccs.html">XMPP</a> (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
-<a href="client.html">Implementing GCM Client</a>.</li>
-</ul>
-
-<h2 id="lifecycle">Lifecycle Flow</h2>
+<h3 id="lifecycle">Lifecycle Flow</h3>
 
 <ul>
   <li><a href="#register">Enable GCM</a>. An Android application running on a
 mobile device registers to receive messages.</li>
-
+ <li><a href="notifications.html">User Notifications</a>. 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.</li>
   <li><a href="#push-process">Send a message</a>. A 3rd-party application
 server sends messages to the device.</li>
   <li><a href="#receiving">Receive a message</a>. An Android application
@@ -194,18 +194,62 @@
 
 <p>These processes are described in more detail below.</p>
 
-<h3 id="register">Enable GCM</h3>
+<h4 id="register">Enable GCM</h4>
 
-<p>The first time the Android application needs to use the messaging service, it
-calls the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a> method {@code register()}, as discussed in
-<a href="client.html">Implementing GCM Client</a>.
-The {@code register()} method returns a registration ID. The Android
-application should store this ID for later use (for instance,
-to check in <code>onCreate()</code> if it is already registered).
+<p>This is the sequence of events that occurs when an Android application
+running on a mobile device registers to receive messages:<span
+class="red-text"></span></p>
+
+<ol>
+  <li>The first time the Android application needs to use the messaging service, it
+fires off a registration Intent to a GCM server.
+    <p>This registration Intent
+(<code>com.google.android.c2dm.intent.REGISTER</code>) includes the sender ID, and the Android application ID.</p>
+<p class="note"><strong>Note:</strong> 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 <code>onCreate()</code>, but only if the application is not registered yet.
 </p>
+  </li>
+  <li>If the registration is successful, the GCM server broadcasts a <code>com.google.android.c2dm.intent.REGISTRATION</code> intent which gives the Android application  a registration
+ID. 
+    <p>The Android application should store this ID for later use (for instance, to check on <code>onCreate()</code> 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 <code>com.google.android.c2dm.intent.REGISTRATION</code> intent may be called
+multiple times. Your Android application needs to be able to respond
+accordingly.</p></li>
+  <li>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. </li>
+</ol>
 
-<h3 id="push-process">Send a message</h3>
+<p>The registration ID lasts until the Android application explicitly unregisters
+itself, or until Google refreshes the registration ID for your Android application.</p>
+
+<p class="note"><strong>Note:</strong> 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 <code>com.google.android.c2dm.intent.RECEIVE</code> intents. At that point, your server should mark the device as unregistered (the server will receive a <code><a href="#unreg_device">NotRegistered</a></code> error).</p>
+  <p>
+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.</p>
+
+
+
+
+<h4 id="push-process">Send a Message</h4>
+
+<p>For an application server to send a  message to an Android application, the following things must be in
+place:</p>
+
+<ul>
+  <li>The Android application has stored a target that it can specify as the recipient of a message. This can be one of the following:
+  <ul>
+    <li>A single registration ID (or an array of registration IDs) that allows the app to receive messages
+for a particular device.</li>
+    <li>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 <a href="notifications.html">User Notifications</a>.</li>
+  </ul>
+    </li>
+
+<li>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
+<a href="#server">Role of the 3rd-party Application Server</a>). Now it will
+get used to send messages to the device. </li>
+</ul>
 
 <p>Here is the sequence of events that occurs when the application server sends a 
 message:</p>
@@ -220,14 +264,13 @@
 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.</li>
   <li>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.</li>
+non-trivial processing, you may want to grab a {@link android.os.PowerManager.WakeLock} and do any processing in a Service.</li>
 </ol>
 
 <p> An Android application can  unregister GCM if it no longer wants to receive 
 messages.</p>
 
-<h3 id="receiving">Receive a message</h3>
+<h4 id="receiving">Receive a Message</h4>
 
 <p>This is the sequence of events that occurs when an Android application
 installed on a mobile device receives a message:</p>
@@ -239,8 +282,482 @@
 in a <code>com.google.android.c2dm.intent.RECEIVE</code> Intent as a set of
 extras.</li>
   <li>The Android application extracts the raw data
-from the <code>com.google.android.c2dm.intent.RECEIVE</code><code> </code>Intent
-by key and processes the data.</li>
+from the <code>com.google.android.c2dm.intent.RECEIVE</code><code> </code>Intent by key and processes the data.</li>
 </ol>
 
+<h3 id="user">What Does the User See?</h3>
+
+<p>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. </p>
+
+
+<h2 id="server">Role of the 3rd-party Application Server</h2>
+
+<p>Before you can write client Android applications that use the GCM feature, you must
+have an  application server that meets the following criteria:</p>
+
+<ul>
+  <li>Able to communicate with your client.</li>
+  <li>Able to  fire off HTTPS requests to the GCM server.</li>
+  <li>Able to handle requests and resend them as needed, using <a href="http://en.wikipedia.org/wiki/Exponential_backoff">exponential back-off.</a></li>
+  <li>Able to store the API key and client registration IDs. The
+API key is included in the header of POST requests that send
+messages.</li>
+</ul>
+
+<h3 id="send-msg">Sending Messages</h3>
+<p>This section describes how the 3rd-party application server sends messages to one or more mobile devices. Note the following:</p>
+<ul>
+  <li>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 <em>multicast message</em>.</li>
+  <li>To send a single message to multiple devices owned by a single user, you can use a {@code notification_key}, as described in <a href="notifications.html">User Notifications</a>.
+  
+  <li>You have 2 choices in how you construct requests and responses: plain text or JSON.</li>
+  <li>However, to send multicast messages, you must use JSON. Plain text will not work.</li>
+</ul>
+<p>Before the 3rd-party application server can send a  message to an
+  Android application, it must have received a registration ID from it.</p>
+<h4 id="request">Request format</h4>
+<p>To send a  message, the application server issues a POST request to <code>https://android.googleapis.com/gcm/send</code>.</p>
+<p>A  message request is made of 2 parts: HTTP header and HTTP body.</p>
+
+<p>The HTTP header must contain the following headers:</p>
+<ul>
+  <li><code>Authorization</code>: key=YOUR_API_KEY</li>
+  <li><code>Content-Type</code>: <code>application/json</code> for JSON; <code>application/x-www-form-urlencoded;charset=UTF-8</code> for plain text.
+  </li>
+</ul>
+
+<p>For example:
+</p>
+<pre>Content-Type:application/json
+Authorization:key=AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA
+
+{
+  "registration_ids" : ["APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx..."],
+  "data" : {
+    ...
+  },
+}</pre>
+<p class="note">
+  <p><strong>Note:</strong> If <code>Content-Type</code> is omitted, the format is assumed to be plain text.</p>
+</p>
+
+<p>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:</p>
+<table>
+  <tr>
+    <th>Field</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td><code>registration_ids</code></td>
+    <td>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&mdash;this can be either a registration ID, an array of registration IDs, or a {@code notification_key}.</td>
+  </tr>
+ <tr>
+    <td><code>notification_key</code></td>
+    <td>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 <a href="notifications.html">User Notifications</a>. Optional.</td>
+  </tr>
+
+<tr>
+    <td><code>notification_key_name</code></td>
+    <td>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 <code>notification_key_name</code> 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 <a href="notifications.html">User Notifications</a>.</td>
+  </tr>
+
+  <tr>
+    <td><code>collapse_key</code></td>
+    <td>An arbitrary string (such as &quot;Updates Available&quot;) 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 &quot;last&quot; message may not actually be the last
+message sent by the application server. See <a href="adv.html#collapsible">Advanced Topics</a> for more discussion of this topic. Optional.</td>
+  </tr>
+  <tr>
+    <td><code>data</code></td>
+    <td>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, <code>"data":{"score":"3x1"}</code> would result in an intent extra named <code>score</code> whose value is the string <code>3x1</code>. 
+
+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 (<code>from</code> or any word starting with <code>google.</code>). To complicate things slightly, there are some reserved words (such as <code>collapse_key</code>) 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.</td>
+
+
+  </tr>
+  <tr>
+    <td><code>delay_while_idle</code></td>
+    <td>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 <code>collapse_key</code> value will be
+sent. Optional. The default value is <code>false</code>, and must be a JSON boolean.</td>
+  </tr>
+  <tr>
+    <td><code>time_to_live</code></td>
+    <td>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).</td>
+  </tr>
+<tr>
+  <td><code>restricted_package_name</code></td>
+  <td>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.
+  </td>
+</tr>
+<tr>
+  <td><code>dry_run</code></td>
+  <td>If included, allows developers to test their request without actually sending a message. Optional. The default value is <code>false</code>, and must be a JSON boolean.
+  </td>
+</tr>
+</table>
+
+<p>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:
+<table>
+  <tr>
+    <th>Field</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td><code>registration_id</code></td>
+    <td>Must contain the registration ID of the single device receiving the message. Required.</td>
+  </tr>
+  <tr>
+    <td><code>collapse_key</code></td>
+    <td>Same as JSON (see previous table). Optional.</td>
+  </tr>
+  <tr>
+    <td><code>data.&lt;key&gt;</code></td>
+
+    <td>Payload data, expressed as parameters prefixed with <code>data.</code> and suffixed as the key. For instance, a parameter of <code>data.score=3x1</code> would result in an intent extra named <code>score</code> whose value is the string <code>3x1</code>. 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 (<code>from</code> or any word starting with 
+<code>google.</code>). To complicate things slightly, there are some reserved words (such as <code>collapse_key</code>) 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.</td>
+
+  </tr>
+  <tr>
+    <td><code>delay_while_idle</code></td>
+    <td>Should be represented as <code>1</code> or <code>true</code> for <code>true</code>, anything else for <code>false</code>. Optional. The default value is <code>false</code>.</td>
+  </tr>
+  <tr>
+    <td><code>time_to_live</code></td>
+    <td>Same as JSON (see previous table). Optional.</td>
+  </tr>
+<tr>
+  <td><code>restricted_package_name</code></td>
+  <td>Same as JSON (see previous table). Optional.
+  </td>
+</tr>
+<tr>
+  <td><code>dry_run</code></td>
+  <td>Same as JSON (see previous table). Optional.
+  </td>
+</tr>
+</table>
+
+<p>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 <code>dry_run</code> with the value <code>true</code>. 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 <a href="#response">Response format</a>).</p>
+
+  <h4 id="example-requests">Example requests</h4>
+  <p>Here is the smallest possible request (a message without any parameters and just one recipient) using JSON:</p>
+  <pre class="prettyprint pretty-json">{ &quot;registration_ids&quot;: [ &quot;42&quot; ] }</pre>
+  
+  <p>And here the same example using plain text:</p>
+  <pre class="prettyprint">registration_id=42</pre>
+  
+  <p> Here is a message with a payload and 6 recipients:</p>
+  <pre class="prettyprint pretty-HTML">{ "data": {
+    "score": "5x1",
+    "time": "15:10"
+  },
+  "registration_ids": ["4", "8", "15", "16", "23", "42"]
+}</pre>
+  <p>Here is a message with all optional fields set and 6 recipients:</p>
+  <pre class="prettyprint pretty-json">{ "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"]
+}</pre>
+  <p>And here is the same message using plain-text format (but just 1 recipient):  </p>
+  
+  <pre class="prettyprint">collapse_key=score_update&amp;time_to_live=108&amp;delay_while_idle=1&amp;data.score=4x8&amp;data.time=15:16.2342&amp;registration_id=42
+  </pre>
+
+<p class="note"><strong>Note:</strong> 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.</p>
+
+
+<h4 id="response">Response format</h4>
+
+<p>There are two possible outcomes when trying to send a message:</p>
+<ul>
+  <li>The message is processed successfully.</li>
+  <li>The GCM server rejects the request.</li>
+</ul>
+
+<p>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).</p>
+
+<p>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.</p>
+<table border=1>
+  <tr>
+    <th>Response</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>200</td>
+    <td>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 <a href="#success">Interpreting a success response</a> for more details.</td>
+  </tr>
+  <tr>
+    <td>400</td>
+    <td><span id="internal-source-marker_0.2">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.</td>
+  </tr>
+  <tr>
+    <td>401</td>
+    <td>There was an error authenticating the sender account. <a href="#auth_error">Troubleshoot</a></td>
+  </tr>
+  <tr>
+    <td>5xx</td>
+    <td>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 <code>Retry-After</code> header included in the response. Application servers must implement exponential back-off. <a href="#internal_error">Troubleshoot</a></td>
+  </tr>
+</table>
+
+<h4 id="success">Interpreting a success response</h4>
+<p>When a JSON request is successful (HTTP status code 200), the response body contains a JSON object with the following fields:</p>
+<table>
+  <tr>
+    <th>Field</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td><code>multicast_id</code></td>
+    <td>Unique ID (number) identifying the multicast message.</td>
+  </tr>
+  <tr>
+    <td><code>success</code></td>
+    <td>Number of messages that were processed without an error.</td>
+  </tr>
+  <tr>
+    <td><code>failure</code></td>
+    <td>Number of messages that could not be processed.</td>
+  </tr>
+  <tr>
+    <td><code>canonical_ids</code></td>
+    <td>Number of results that contain a canonical registration ID. See <a href="adv.html#canonical">Advanced Topics</a> for more discussion of this topic.</td>
+  </tr>
+  <tr>
+    <td><code>results</code></td>
+    <td>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:<br>
+      <ul>
+        <li><code>message_id</code>: String representing the message when it was successfully processed.</li>
+        <li><code>registration_id</code>: 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.
+        </li>
+        <li><code>error</code>: 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 &quot;Unavailable&quot;  (meaning GCM servers were busy and could not process the message for that  particular recipient, so it could be retried).</li>
+    </ul></td>
+  </tr>
+</table>
+<p>If the value of <code>failure</code> and <code>canonical_ids</code> 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:</p>
+<ul>
+  <li>If <code>message_id</code> is set, check for <code>registration_id</code>:
+    <ul>
+      <li>If <code>registration_id</code> 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</code> passed in the request (using the same index).</li>
+    </ul>
+  </li>
+  <li>Otherwise, get the value of <code>error</code>:
+    <ul>
+      <li>If it is <code>Unavailable</code>, you could retry to send it in another request.</li>
+      <li>If it is <code>NotRegistered</code>, 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 <code>com.google.android.c2dm.intent.RECEIVE</code> intents.</li>
+      <li>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 <a href="#error_codes">Interpreting an error response</a> for all possible error values.</li>
+    </ul>
+  </li>
+</ul>
+
+<p>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 <code>id=<em>ID of sent message</em></code> or <code>Error=<em>GCM error code</em></code>. The second line, if available, 
+has the format of <code>registration_id=<em>canonical ID</em></code>. 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:</p>
+<ul>
+  <li>If first line starts with <code>id</code>, check second line:
+    <ul>
+      <li>If second line starts with <code>registration_id</code>, gets its value and replace the registration IDs in your server database.</li>
+    </ul>
+  </li>
+  <li>Otherwise, get the value of <code>Error</code>:
+    <ul>
+      <li>If it is <code>NotRegistered</code>, remove the registration ID from your server database.</li>
+      <li>Otherwise, there is probably a non-recoverable error (<strong>Note: </strong>Plain-text requests will never return <code>Unavailable</code> as the error code, they would have returned a 500 HTTP status instead).</li>
+    </ul>
+  </li>
+</ul>
+
+<h4 id="error_codes">Interpreting an error response</h4>
+<p>Here are the recommendations for handling the different types of error that might occur when trying to send a message to a device:</p>
+
+<dl>
+<dt id="missing_reg"><strong>Missing Registration ID</strong></dt>
+<dd>Check that the request contains a registration ID (either in the <code>registration_id</code> parameter in a plain text message, or in the <code>registration_ids</code> field in JSON). 
+<br/>Happens when error code is <code>MissingRegistration</code>.</dd>
+
+<dt id="invalid_reg"><strong>Invalid Registration ID</strong></dt>
+<dd>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 <code>com.google.android.c2dm.intent.REGISTRATION</code> intent and that you're not truncating it or adding additional characters. 
+<br/>Happens when error code is <code>InvalidRegistration</code>.</dd>
+
+<dt id="mismatched_sender"><strong>Mismatched Sender</strong></dt>
+<dd>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 <code>MismatchSenderId</code>.</dd>
+
+<dt id="unreg_device"><strong>Unregistered Device</strong></dt>
+<dd>An existing registration ID may cease to be valid in a number of scenarios, including:
+<ul>
+  <li>If the application manually unregisters by issuing a <span class="prettyprint pretty-java"><code>com.google.android.c2dm.intent.UNREGISTER</code></span><code> </code>intent.</li>
+  <li>If the application is automatically unregistered, which can happen (but is not guaranteed) if the user uninstalls the application.</li>
+  <li>If the registration ID expires. Google might decide to refresh registration IDs. </li>
+  <li>If the application is updated but the new version does not have a broadcast receiver configured to receive <code>com.google.android.c2dm.intent.RECEIVE</code> intents.</li>
+</ul>
+For all these cases, you should remove this registration ID from the 3rd-party server and stop using it to send 
+messages. 
+<br/>Happens when error code is <code>NotRegistered</code>.</dd>
+
+<dt id="big_msg"><strong>Message Too Big</strong></dt>
+  <dd>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. 
+<br/>Happens when error code is <code>MessageTooBig</code>.</dd>
+
+<dt id="invalid_datakey"><strong>Invalid Data Key</strong></dt>
+<dd>The payload data contains a key (such as <code>from</code> or any value prefixed by <code>google.</code>) that is used internally by GCM in the  <code>com.google.android.c2dm.intent.RECEIVE</code> Intent and cannot be used. Note that some words (such as <code>collapse_key</code>) are also used by GCM but are allowed in the payload, in which case the payload value will be overridden by the GCM value. 
+<br />
+Happens when the error code is <code>InvalidDataKey</code>.</dd>
+
+<dt id="ttl_error"><strong>Invalid Time To Live</strong></dt>
+  <dd>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 <code>InvalidTtl</code>.
+</dd>
+
+  <dt id="auth_error"><strong>Authentication Error</strong></dt>
+  <dd>The sender account that you're trying to use to send a message couldn't be authenticated. Possible causes are: <ul>
+<li>Authorization header missing or with invalid syntax.</li>
+<li>Invalid project number sent as key.</li>
+<li>Key valid but with GCM service disabled.</li>
+<li>Request originated from a server not whitelisted in the Server Key IPs.</li>
+
+</ul>
+Check that the token you're sending inside the <code>Authorization</code> header is the correct API key associated with your project. You can check the validity of your API key by running the following command:<br/>
+
+<pre># 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\"]}"</pre>
+
+
+
+If you receive a 401 HTTP status code, your API key is not valid. Otherwise you should see something like this:<br/>
+
+<pre>
+{"multicast_id":6782339717028231855,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
+</pre>
+If you want to confirm the validity of a registration ID, you can do so by replacing "ABC" with the registration ID.
+<br/>
+Happens when the HTTP status code is 401.
+
+  <dt id="timeout"><strong>Timeout</strong></dt>
+
+<dd>The server couldn't process the request in time. You should retry the
+same request, but you MUST obey the following requirements:
+
+<ul>
+
+<li>Honor the <code>Retry-After</code> header if it's included in the response from the GCM server.</li>
+        
+        
+<li>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.</li>
+    
+
+Senders that cause problems risk being blacklisted. 
+<br />
+Happens when the HTTP status code is between 501 and 599, or when the <code>error</code> field of a JSON object in the results array is <code>Unavailable</code>.
+</dd>
+
+<dt id="internal_error"><strong>Internal Server Error</strong></dt>
+
+<dd>
+The server encountered an error while trying to process the request. You
+could retry the same request (obeying the requirements listed in the <a href="#timeout">Timeout</a>
+section), but if the error persists, please report the problem in the <a href="https://groups.google.com/forum/?fromgroups#!forum/android-gcm">android-gcm group</a>.
+<br />
+Happens when the HTTP status code is 500, or when the <code>error</code> field of a JSON
+object in the results array is <code>InternalServerError</code>.
+</dd>
+
+<dt id="restricted_package_name"><strong>Invalid Package Name</strong></dt>
+
+<dd>
+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 
+<code>InvalidPackageName</code>.
+</dd>
+
+
+</dl>
+<h4>Example responses</h4>
+<p>This section shows a few examples of responses indicating messages that were processed successfully. See <a href="#example-requests">Example requests</a> for the requests these responses are based on.</p>
+<p> Here is a simple case of a JSON message successfully sent to one recipient without canonical IDs in the response:</p>
+<pre class="prettyprint pretty-json">{ "multicast_id": 108,
+  "success": 1,
+  "failure": 0,
+  "canonical_ids": 0,
+  "results": [
+    { "message_id": "1:08" }
+  ]
+}</pre>
+
+<p>Or if the request was in plain-text format:</p>
+<pre class="prettyprint">id=1:08
+</pre>
+
+<p>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:</p>
+<pre class="prettyprint pretty-json">{ "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"}
+  ]
+}
+</pre>
+<p> In this example:</p>
+<ul>
+  <li>First message: success, not required.</li>
+  <li>Second message: should be resent (to registration ID 8).</li>
+  <li>Third message: had an unrecoverable error (maybe the value got corrupted in the database).</li>
+  <li>Fourth message: success, nothing required.</li>
+  <li>Fifth message: success, but the registration ID should be updated in the server database (from 23 to 32).</li>
+  <li>Sixth message: registration ID (42) should be removed from the server database because the application was uninstalled from the device.</li>
+</ul>
+<p>Or if just the 4th message above was sent using plain-text format:</p>
+<pre class="prettyprint">Error=InvalidRegistration
+</pre>
+<p>If the 5th message above was also sent using plain-text format:</p>
+<pre class="prettyprint">id=1:2342
+registration_id=32
+</pre>
+
+<h3 id="stats">Viewing Statistics</h3>
+
+<p>To view  statistics and any error messages for your GCM applications:</p>
+<ol>
+  <li> Go to the <code><a href="http://play.google.com/apps/publish">Developer Console</a></code>.</li>
+  <li>Login with your developer account. 
+  <p>You will see a page that has a list of all of your apps.</p></li>
+  <li> Click on the &quot;statistics&quot; link next to the app for which you want to view GCM stats. 
+  <p>Now you are on the statistics page.</p> </li>
+  <li>Go to the drop-down menu and select the GCM metric you want to view. 
+  </li>
+</ol>
+<p class="note"><strong>Note:</strong> Stats on the Google API Console are not enabled for GCM. You must use the <a href="http://play.google.com/apps/publish">Developer Console</a>.</p>
+
 
diff --git a/docs/html/google/gcm/gs.jd b/docs/html/google/gcm/gs.jd
index f6b7ebe..8ceea0cc 100644
--- a/docs/html/google/gcm/gs.jd
+++ b/docs/html/google/gcm/gs.jd
@@ -1,4 +1,4 @@
-page.title=Getting Started
+page.title=Getting Started with GCM
 page.tags="cloud","push","messaging"
 @jd:body
 
@@ -12,7 +12,8 @@
 <li><a href="#create-proj">Creating a Google API Project</a></li>
 <li><a href="#gcm-service">Enabling the GCM Service</a></li>
 <li><a href="#access-key">Obtaining an API Key</a></li>
-<li><a href="#next">Next Steps</a></li>
+<li><a href="#client">Writing a Client App</a></li>
+<li><a href="#server">Writing the Server Code</a></li>
 </ol>
 
 <h2>See Also</h2>
@@ -25,12 +26,12 @@
 </div>
 </div>
 
-<p>This document tells you how to get started setting up a GCM
+<p>The sections below guide you through the process of setting up a GCM
 implementation.
-Before you begin, make sure to <a href="/google/play-services/setup.html">set up
-the Google Play Services SDK</a>. You need this SDK to use the
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a> methods.</p>
+Before you start, make sure to <a href="/google/play-services/setup.html">set up
+the Google Play Services SDK</a>. You need this SDK to use the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> methods.</p>
+
+<p>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.</p>
 
 
 <h2 id="create-proj">Creating a Google API project</h2>
@@ -40,17 +41,13 @@
   </li>
   <li>If you haven't created an API project yet, this page will prompt you to do so:
   <p><img src="{@docRoot}images/gcm/gcm-create-api-proj.png" class="screenshot" /></p>
-<p class="note"><strong>Note:</strong> If you already have existing projects,
-the first page you see will be the <strong>Dashboard</strong> page. From there
-you can create a new project by opening the project drop-down menu (upper left corner)
-and choosing <strong>Other projects > Create</strong>.</p></li>
+<p class="note"><strong>Note:</strong> If you already have existing projects, the first page you see will be the <strong>Dashboard</strong> page. From there you can create a new project by opening the project drop-down menu (upper left corner) and choosing <strong>Other projects > Create</strong>.</p></li>
   <li> Click <strong>Create project</strong>.
     Your browser URL will change to something like:</li>
 
 <pre> https://code.google.com/apis/console/#project:<strong>4815162342</strong></pre>
 
-  <li> Take note of the value after <code>#project:</code> (4815162342 in this
-example). This is your project number, and it will be used later on as the GCM sender ID.</li>
+  <li> Take note of the value after <code>#project:</code> (4815162342 in this example). This is your project number, and it will be used later on as the GCM sender ID.</li>
   
 </ol>
 <h2 id="gcm-service">Enabling the GCM Service</h2>
@@ -64,53 +61,463 @@
 <h2 id="access-key">Obtaining an API Key</h2>
 <p>To obtain an API  key:</p>
 <ol>
-  <li> In the main Google APIs Console page, select <strong>API Access</strong>.
-You will see a screen that resembles the following:</li>
+  <li> In the main Google APIs Console page, select <strong>API Access</strong>. You will see a screen that resembles the following:</li><br />
 
 
-<img src="{@docRoot}images/gcm/gcm-api-access.png" style="width:400px;padding:4px;">
-
-  <li>Click  <strong>Create new Server key</strong>. 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:</li>
+<img src="{@docRoot}images/gcm/gcm-api-access.png" style="width:400px;padding:4px;margin-bottom:0em;">
 
 
-<img src="{@docRoot}images/gcm/gcm-config-server-key.png" style="width:400px;padding:4px;">
+  <li>Click  <strong>Create new Server key</strong>. 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:</li><br />
+
+
+<img src="{@docRoot}images/gcm/gcm-config-server-key.png" style="width:400px;padding:4px;margin-bottom:0em;">
 
   
-  <li>Click <strong>Create</strong>:</li>
+  <li>Click <strong>Create</strong>:</li><br />
   
 
-<img src="{@docRoot}images/gcm/gcm-api-key.png" style="width:400px;padding:4px;">
+<img src="{@docRoot}images/gcm/gcm-api-key.png" style="width:400px;padding:4px;margin-bottom:0em;">
 
 
 
 </ol>
-<p> Take note of the <strong>API key</strong> value (<code>YourKeyWillBeShownHere</code>)
-in this example, as it will be used later on.</p>
-<p class="note"><strong>Note:</strong> If you need to rotate the key, click
-<strong>Generate new key</strong>. 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 <strong>Delete key</strong>.</p>
+<p> Take note of the <strong>API key</strong> value (<code>YourKeyWillBeShownHere</code>) in this example, as it will be used later on.</p>
+<p class="note"><strong>Note:</strong> If you need to rotate the key, click  <strong>Generate new key</strong>. 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 <strong>Delete key</strong>.</p>
 
-<h2 id="next">Next Steps</h2>
+<p>The following sections walk you through the steps of creating client and server-side code.</p>
 
-<p>Once you've finished the tasks listed above, you're ready to start
-implementing GCM. Here is an overview of the basic steps:</p>
+<h2 id="client">Writing a Client App</h2>
 
-<ol>
-  <li>Decide which Google-provided GCM connection server you want to use&mdash;
-  <a href="http.html">HTTP</a> or <a href="ccs.html">XMPP</a> (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. </li>
-  <li>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 <a href="server.html">
-Implementing GCM Server</a>.</li>
-<li>Write your client app. This is the GCM-enabled Android application that runs
-on a device. See <a href="client.html">Implementing GCM Client</a> for more information.</li>
-</ol>
+<p>This section walks you through the steps involved in writing a client-side application&mdash;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 <a href="#server">Writing the Server Code</a>, below.</p>
+
+
+
+<h3 id="manifest">Step 1: Edit Your App's Manifest</h3>
+<ul>
+  <li>The <code>com.google.android.c2dm.permission.RECEIVE</code> permission so the Android application can register and receive messages.</li>
+  <li>The <code>android.permission.INTERNET</code> permission so the Android application can send the registration ID to the 3rd party server.</li>
+  <li>The <code>android.permission.GET_ACCOUNTS</code> permission as GCM requires a Google account (necessary only if if the device is running a version lower than Android 4.0.4)</li>
+  <li>The <code>android.permission.WAKE_LOCK</code> permission so the application can keep the processor from sleeping when a message is received. Optional&mdash;use only if the app wants to keep the device from sleeping.</li>
+  <li>An <code>applicationPackage + &quot;.permission.C2D_MESSAGE&quot;</code> permission to prevent other Android applications from registering and receiving the Android application's
+messages. The permission name must exactly match this pattern&mdash;otherwise the Android application will not receive the messages.</li>
+   <li>A receiver for <code>com.google.android.c2dm.intent.RECEIVE</code>, with the category set
+as <code>applicationPackage</code>. The receiver should require the <code>com.google.android.c2dm.SEND</code> permission, so that only the GCM
+Framework can send a message to it. Note that the receiving
+of messages is implemented as an <a href="{@docRoot}guide/components/intents-filters.html">intent</a>.</li>
+  <li>An intent service to handle the intents received by the broadcast receiver. Optional.</li>
+  <li>If the GCM feature is critical to the Android application's function, be sure to
+set <code>android:minSdkVersion=&quot;8&quot;</code> in the manifest. This
+ensures that the Android application cannot be installed in an environment in which it
+could not run properly. </li>
+</ul>
+
+<p>Here are excerpts from a manifest that supports GCM:</p>
+
+<pre class="prettyprint pretty-xml">
+&lt;manifest package="com.example.gcm" ...&gt;
+
+    &lt;uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/&gt;
+    &lt;uses-permission android:name="android.permission.INTERNET" /&gt;
+    &lt;uses-permission android:name="android.permission.GET_ACCOUNTS" /&gt;
+    &lt;uses-permission android:name="android.permission.WAKE_LOCK" /&gt;
+    &lt;uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /&gt;
+
+    &lt;permission android:name="com.example.gcm.permission.C2D_MESSAGE" 
+        android:protectionLevel="signature" /&gt;
+    &lt;uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" /&gt;
+
+    &lt;application ...&gt;
+        &lt;receiver
+            android:name=".MyBroadcastReceiver"
+            android:permission="com.google.android.c2dm.permission.SEND" &gt;
+            &lt;intent-filter&gt;
+                &lt;action android:name="com.google.android.c2dm.intent.RECEIVE" /&gt;
+                &lt;category android:name="com.example.gcm" /&gt;
+            &lt;/intent-filter&gt;
+        &lt;/receiver&gt;
+        &lt;service android:name=".MyIntentService" /&gt;
+    &lt;/application&gt;
+
+&lt;/manifest&gt;
+</pre>
+
+
+<h3 id="register">Step 2: Register for GCM</h3>
+
+<p>An Android application running on a mobile device registers to receive messages by calling 
+the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> method 
+<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html#register">{@code register(senderID...)}</a>.
+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.</p>
+
+<h3 id="app"> Step 3: Write Your Application</h3>
+
+<p>Finally, write your application. GCM offers a variety of ways to get the job done:</p>
+
+<ul>
+  <li>For your messaging server, you can either use the new <a href="ccs.html">GCM Cloud Connection Server</a> (CCS), the older <a href="gcm.html">GCM HTTP server</a>, or both in tandem. For more discussion, see see <a href="server.html">GCM Server</a>.</li>
+  <li>To write your client application (that is, the GCM-enabled app that runs on an Android device), use the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> APIs as shown below. Don't forget to set up your project to use the Google Play services SDK as described in <a href="/google/play-services/setup.html">Setup Google Play Services SDK</a>.</li>
+</ul>
+</li>
+  
+</ul>
+
+<h4 id="example">Example</h4>
+
+<p>Here is a sample client application that illustrates how to use the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> 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 <a href="#server">Writing the Server Code</a>.</p>
+
+<p>Note the following:</p>
+
+<ul>
+  <li>The sample primarily illustrates two things: registration, and upstream messaging. Upstream messaging only applies to apps that are running against a <a href="ccs.html">CCS</a> server; HTTP-based servers don't support upstream messaging.</li>
+  <li>The <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> 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 <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> registration APIs, regardless of your underlying server.</li>
+</ul>
+
+<h5>Registering</h5>
+<p>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:</p>
+
+<pre>/**
+ * 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;
+
+    &#64;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);
+    }
+...
+}</pre>
+
+<p>The app calls {@code getRegistrationId()} to see whether there is an existing registration ID stored in shared preferences:</p>
+
+<pre>/**
+ * Gets the current registration id for application on GCM service.
+ * &lt;p&gt;
+ * If result is empty, the registration has failed.
+ *
+ * &#64;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;
+}
+
+...
+
+/**
+ * &#64;return Application's {&#64;code SharedPreferences}.
+ */
+private SharedPreferences getGCMPreferences(Context context) {
+    return getSharedPreferences(DemoActivity.class.getSimpleName(), 
+            Context.MODE_PRIVATE);
+}</pre>
+
+<p>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:</p>
+
+<pre>/**
+ * &#64;return Application's version code from the {&#64;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.
+ *
+ * &lt;p&gt;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.
+ *
+ * &#64;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;
+}</pre>
+
+
+<p>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:</p>
+
+<pre>
+/**
+ * Registers the application with GCM servers asynchronously.
+ * &lt;p&gt;
+ * Stores the registration id, app versionCode, and expiration time in the 
+ * application's shared preferences.
+ */
+private void registerBackground() {
+    new AsyncTask<Void, Void, String>() {
+        &#64;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;
+        }
+
+        &#64;Override
+        protected void onPostExecute(String msg) {
+            mDisplay.append(msg + "\n");
+        }
+    }.execute(null, null, null);
+}</pre>
+
+<p>After registering, the app calls {@code setRegistrationId()} to store the registration ID in shared preferences for future use:</p>
+
+<pre>/**
+ * Stores the registration id, app versionCode, and expiration time in the
+ * application's {&#64;code SharedPreferences}.
+ *
+ * &#64;param context application's context.
+ * &#64;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();
+}</pre>
+
+<h5>Sending a message</h5>
+<p>When the user clicks the app's <strong>Send</strong> button, the app sends an upstream message using the new <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">{@code GoogleCloudMessaging}</a> APIs. In order to receive the upstream message, your server should be connected to CCS. You can use the code shown in <a href="#server">Writing the Server Code</a> as a sample XMPP client to connect to CCS.</p>
+
+<pre>public void onClick(final View view) {
+    if (view == findViewById(R.id.send)) {
+        new AsyncTask<Void, Void, String>() {
+            &#64;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 + "&#64;gcm.googleapis.com", id, data);
+                    msg = "Sent message";
+                } catch (IOException ex) {
+                    msg = "Error :" + ex.getMessage();
+                }
+                return msg;
+            }
+
+            &#64;Override
+            protected void onPostExecute(String msg) {
+                mDisplay.append(msg + "\n");
+            }
+        }.execute(null, null, null);
+    } else if (view == findViewById(R.id.clear)) {
+        mDisplay.setText("");
+    } 
+}</pre>
+
+<p>As described above in <a href="#manifest">Step 1</a>, the app includes a broadcast receiver for the <code>com.google.android.c2dm.intent.RECEIVE</code> 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:</p>
+
+<pre>/**
+ * 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;
+    &#64;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());
+    }
+}</pre>
+
+<h2 id="server">Writing the Server Code</h2>
+
+<p>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 <a href="server.html">GCM Server</a>.</p>
+
+<pre>
+#!/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 = (&quot;&lt;message&gt;&lt;gcm xmlns='google:mobile:data'&gt;{1}&lt;/gcm&gt;&lt;/message&gt;&quot;)
+  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 &gt; 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()</pre>
+
 
 
diff --git a/docs/html/google/gcm/http.jd b/docs/html/google/gcm/http.jd
deleted file mode 100644
index b8d8659d..0000000
--- a/docs/html/google/gcm/http.jd
+++ /dev/null
@@ -1,618 +0,0 @@
-page.title=GCM HTTP Connection Server
-@jd:body
-
-<div id="qv-wrapper">
-<div id="qv">
-
-
-<h2>In this document</h2>
-
-<ol class="toc">
-  <li><a href="#auth">Authentication</a> </li>
-  <li><a href="#request">Request Format</a> </li>
-  <li><a href="#response">Response Format</a>
-  <ol class="toc">
-    <li><a href="#success">Interpreting a success response</a>
-    <li><a href="#error_codes">Interpreting an error response</a>
-    <li><a href="#example-responses">Example responses</a>
-  </ol>
-  </li>
-  <li><a href="#app-server">Implementing an HTTP-Based App Server</a>
-</ol>
-
-<h2>See Also</h2>
-
-<ol class="toc">
-<li><a href="gs.html">Getting Started</a></li>
-<li><a href="client.html">Implementing GCM Client</a></li>
-<li><a href="ccs.html">Cloud Connection Server</a></li>
-
-
-</ol>
-
-</div>
-</div>
-
-<p>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.</p>
-
-
-
-<p class="note"><strong>Note:</strong> See
-<a href="server.html#params">Implementing GCM Server</a> for a list of all the message
-parameters and which connection server(s) supports them.</p>
-
-
-<h2 id="auth">Authentication</h2>
-
-<p>To send a  message, the application server issues a POST request to
-<code>https://android.googleapis.com/gcm/send</code>.</p>
-<p>A  message request is made of 2 parts: HTTP header and HTTP body.</p>
-
-<p>The HTTP header must contain the following headers:</p>
-<ul>
-  <li><code>Authorization</code>: key=YOUR_API_KEY</li>
-  <li><code>Content-Type</code>: <code>application/json</code> for JSON; <code>application/x-www-form-urlencoded;charset=UTF-8</code> for plain text.
-  </li>
-</ul>
-
-<p>For example:
-</p>
-<pre>Content-Type:application/json
-Authorization:key=AIzaSyB-1uEai2WiUapxCs2Q0GZYzPu7Udno5aA
-
-{
-  "registration_ids" : ["APA91bHun4MxP5egoKMwt2KZFBaFUH-1RYqx..."],
-  "data" : {
-    ...
-  },
-}</pre>
-<p class="note">
-  <p><strong>Note:</strong> If <code>Content-Type</code> is omitted, the format
-is assumed to be plain text.</p>
-</p>
-
-<p>The HTTP body content depends on whether you're using JSON or plain text.
-See
-<a href="server.html#params">Implementing GCM Server</a> for a list of all the
-parameters your JSON or plain text message can contain.</p>
-
-
-  <h2 id="request">Request Format</h2>
-  <p>Here is the smallest possible request (a message without any parameters and
-just one recipient) using JSON:</p>
-  <pre class="prettyprint pretty-json">{ &quot;registration_ids&quot;: [ &quot;42&quot; ] }</pre>
-
-  <p>And here the same example using plain text:</p>
-  <pre class="prettyprint">registration_id=42</pre>
-
-  <p> Here is a message with a payload and 6 recipients:</p>
-  <pre class="prettyprint pretty-HTML">{ "data": {
-    "score": "5x1",
-    "time": "15:10"
-  },
-  "registration_ids": ["4", "8", "15", "16", "23", "42"]
-}</pre>
-  <p>Here is a message with all optional fields set and 6 recipients:</p>
-  <pre class="prettyprint pretty-json">{ "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"]
-}</pre>
-  <p>And here is the same message using plain-text format (but just 1 recipient):  </p>
-
-  <pre class="prettyprint">collapse_key=score_update&amp;time_to_live=108&amp;delay_while_idle=1&amp;data.score=4x8&amp;data.time=15:16.2342&amp;registration_id=42
-  </pre>
-
-<p class="note"><strong>Note:</strong> 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.</p>
-
-
-
-<h2 id="response">Response format</h2>
-
-<p>There are two possible outcomes when trying to send a message:</p>
-<ul>
-  <li>The message is processed successfully.</li>
-  <li>The GCM server rejects the request.</li>
-</ul>
-
-<p>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).</p>
-
-<p>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.</p>
-<table border=1>
-  <tr>
-    <th>Response</th>
-    <th>Description</th>
-  </tr>
-  <tr>
-    <td>200</td>
-    <td>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 <a href="#success">Interpreting a success response</a>
-for more details.</td>
-  </tr>
-  <tr>
-    <td>400</td>
-    <td><span id="internal-source-marker_0.2">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.</td>
-  </tr>
-  <tr>
-    <td>401</td>
-    <td>There was an error authenticating the sender account.
-<a href="#auth_error">Troubleshoot</a></td>
-  </tr>
-  <tr>
-    <td>5xx</td>
-    <td>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 <code>Retry-After</code> header included in the
-response. Application servers must implement exponential back-off.
-<a href="#internal_error">Troubleshoot</a></td>
-  </tr>
-</table>
-
-<h3 id="success">Interpreting a success response</h3>
-<p>When a JSON request is successful (HTTP status code 200), the response body
-contains a JSON object with the following fields:</p>
-<table>
-  <tr>
-    <th>Field</th>
-    <th>Description</th>
-  </tr>
-  <tr>
-    <td><code>multicast_id</code></td>
-    <td>Unique ID (number) identifying the multicast message.</td>
-  </tr>
-  <tr>
-    <td><code>success</code></td>
-    <td>Number of messages that were processed without an error.</td>
-  </tr>
-  <tr>
-    <td><code>failure</code></td>
-    <td>Number of messages that could not be processed.</td>
-  </tr>
-  <tr>
-    <td><code>canonical_ids</code></td>
-    <td>Number of results that contain a canonical registration ID. See
-<a href="adv.html#canonical">Advanced Topics</a> for more discussion of this topic.</td>
-  </tr>
-  <tr>
-    <td><code>results</code></td>
-    <td>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:<br>
-      <ul>
-        <li><code>message_id</code>: String representing the message when it was
-successfully processed.</li>
-        <li><code>registration_id</code>: 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.
-        </li>
-        <li><code>error</code>: 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 &quot;Unavailable&quot;  (meaning GCM servers
-were busy and could not process the message for that  particular recipient, so
-it could be retried).</li>
-    </ul></td>
-  </tr>
-</table>
-<p>If the value of <code>failure</code> and <code>canonical_ids</code> 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:</p>
-<ul>
-  <li>If <code>message_id</code> is set, check for <code>registration_id</code>:
-    <ul>
-      <li>If <code>registration_id</code> 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</code> passed in the request (using the same index).</li>
-    </ul>
-  </li>
-  <li>Otherwise, get the value of <code>error</code>:
-    <ul>
-      <li>If it is <code>Unavailable</code>, you could retry to send it in another
-request.</li>
-      <li>If it is <code>NotRegistered</code>, 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
-<code>com.google.android.c2dm.intent.RECEIVE</code> intents.</li>
-      <li>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 <a href="#error_codes">Interpreting
-an error response</a> for all possible error values.</li>
-    </ul>
-  </li>
-</ul>
-
-<p>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 <code>id=<em>ID of
-sent message</em></code> or <code>Error=<em>GCM error code</em></code>. The second
-line, if available,
-has the format of <code>registration_id=<em>canonical ID</em></code>. 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:</p>
-<ul>
-  <li>If first line starts with <code>id</code>, check second line:
-    <ul>
-      <li>If second line starts with <code>registration_id</code>, gets its value
-and replace the registration IDs in your server database.</li>
-    </ul>
-  </li>
-  <li>Otherwise, get the value of <code>Error</code>:
-    <ul>
-      <li>If it is <code>NotRegistered</code>, remove the registration ID from
-your server database.</li>
-      <li>Otherwise, there is probably a non-recoverable error (<strong>Note:
-</strong>Plain-text requests will never return <code>Unavailable</code> as the
-error code, they would have returned a 500 HTTP status instead).</li>
-    </ul>
-  </li>
-</ul>
-
-<h3 id="error_codes">Interpreting an error response</h3>
-<p>Here are the recommendations for handling the different types of error that
-might occur when trying to send a message to a device:</p>
-
-<dl>
-<dt id="missing_reg"><strong>Missing Registration ID</strong></dt>
-<dd>Check that the request contains a registration ID (either in the
-<code>registration_id</code> parameter in a plain text message, or in the
-<code>registration_ids</code> field in JSON).
-<br/>Happens when error code is <code>MissingRegistration</code>.</dd>
-
-<dt id="invalid_reg"><strong>Invalid Registration ID</strong></dt>
-<dd>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
-<code>com.google.android.c2dm.intent.REGISTRATION</code> intent and that you're
-not truncating it or adding additional characters.
-<br/>Happens when error code is <code>InvalidRegistration</code>.</dd>
-
-<dt id="mismatched_sender"><strong>Mismatched Sender</strong></dt>
-<dd>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 <code>MismatchSenderId</code>.</dd>
-
-<dt id="unreg_device"><strong>Unregistered Device</strong></dt>
-<dd>An existing registration ID may cease to be valid in a number of scenarios, including:
-<ul>
-  <li>If the application manually unregisters by issuing a
-<span class="prettyprint pretty-java">
-<code>com.google.android.c2dm.intent.UNREGISTER</code></span><code>
-</code>intent.</li>
-  <li>If the application is automatically unregistered, which can happen
-(but is not guaranteed) if the user uninstalls the application.</li>
-  <li>If the registration ID expires. Google might decide to refresh registration
-IDs. </li>
-  <li>If the application is updated but the new version does not have a broadcast
-receiver configured to receive <code>com.google.android.c2dm.intent.RECEIVE</code>
-intents.</li>
-</ul>
-For all these cases, you should remove this registration ID from the 3rd-party
-server and stop using it to send
-messages.
-<br/>Happens when error code is <code>NotRegistered</code>.</dd>
-
-<dt id="big_msg"><strong>Message Too Big</strong></dt>
-  <dd>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.
-<br/>Happens when error code is <code>MessageTooBig</code>.</dd>
-
-<dt id="invalid_datakey"><strong>Invalid Data Key</strong></dt>
-<dd>The payload data contains a key (such as <code>from</code> or any value
-prefixed by <code>google.</code>) that is used internally by GCM in the
-<code>com.google.android.c2dm.intent.RECEIVE</code> Intent and cannot be used.
-Note that some words (such as <code>collapse_key</code>) are also used by GCM
-but are allowed in the payload, in which case the payload value will be
-overridden by the GCM value.
-<br />
-Happens when the error code is <code>InvalidDataKey</code>.</dd>
-
-<dt id="ttl_error"><strong>Invalid Time To Live</strong></dt>
-  <dd>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 <code>InvalidTtl</code>.
-</dd>
-
-  <dt id="auth_error"><strong>Authentication Error</strong></dt>
-  <dd>The sender account that you're trying to use to send a message couldn't be
-authenticated. Possible causes are: <ul>
-<li>Authorization header missing or with invalid syntax.</li>
-<li>Invalid project number sent as key.</li>
-<li>Key valid but with GCM service disabled.</li>
-<li>Request originated from a server not whitelisted in the Server Key IPs.</li>
-
-</ul>
-Check that the token you're sending inside the <code>Authorization</code> header
-is the correct API key associated with your project. You can check the validity
-of your API key by running the following command:<br/>
-
-<pre># 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\"]}"</pre>
-
-
-
-If you receive a 401 HTTP status code, your API key is not valid. Otherwise you
-should see something like this:<br/>
-
-<pre>
-{"multicast_id":6782339717028231855,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
-</pre>
-If you want to confirm the validity of a registration ID, you can do so by
-replacing "ABC" with the registration ID.
-<br/>
-Happens when the HTTP status code is 401.
-
-  <dt id="timeout"><strong>Timeout</strong></dt>
-
-<dd>The server couldn't process the request in time. You should retry the
-same request, but you MUST obey the following requirements:
-
-<ul>
-
-<li>Honor the <code>Retry-After</code> header if it's included in the response
-from the GCM server.</li>
-
-
-<li>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.</li>
-
-
-Senders that cause problems risk being blacklisted.
-<br />
-Happens when the HTTP status code is between 501 and 599, or when the
-<code>error</code> field of a JSON object in the results array is <code>Unavailable</code>.
-</dd>
-
-<dt id="internal_error"><strong>Internal Server Error</strong></dt>
-
-<dd>
-The server encountered an error while trying to process the request. You
-could retry the same request (obeying the requirements listed in the <a href="#timeout">Timeout</a>
-section), but if the error persists, please report the problem in the
-<a href="https://groups.google.com/forum/?fromgroups#!forum/android-gcm">android-gcm group</a>.
-<br />
-Happens when the HTTP status code is 500, or when the <code>error</code> field of a JSON
-object in the results array is <code>InternalServerError</code>.
-</dd>
-
-<dt id="restricted_package_name"><strong>Invalid Package Name</strong></dt>
-
-<dd>
-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
-<code>InvalidPackageName</code>.
-</dd>
-</dl>
-
-<h3 id="example-responses">Example responses</h3>
-<p>This section shows a few examples of responses indicating messages that were
-processed successfully. See <a href="#request">Request Format</a> for
-the requests these responses are based on.</p>
-<p> Here is a simple case of a JSON message successfully sent to one recipient
-without canonical IDs in the response:</p>
-<pre class="prettyprint pretty-json">{ "multicast_id": 108,
-  "success": 1,
-  "failure": 0,
-  "canonical_ids": 0,
-  "results": [
-    { "message_id": "1:08" }
-  ]
-}</pre>
-
-<p>Or if the request was in plain-text format:</p>
-<pre class="prettyprint">id=1:08
-</pre>
-
-<p>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:</p>
-<pre class="prettyprint pretty-json">{ "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"}
-  ]
-}
-</pre>
-<p> In this example:</p>
-<ul>
-  <li>First message: success, not required.</li>
-  <li>Second message: should be resent (to registration ID 8).</li>
-  <li>Third message: had an unrecoverable error (maybe the value got corrupted
-in the database).</li>
-  <li>Fourth message: success, nothing required.</li>
-  <li>Fifth message: success, but the registration ID should be updated in the
-server database (from 23 to 32).</li>
-  <li>Sixth message: registration ID (42) should be removed from the server database
-because the application was uninstalled from the device.</li>
-</ul>
-<p>Or if just the 4th message above was sent using plain-text format:</p>
-<pre class="prettyprint">Error=InvalidRegistration
-</pre>
-<p>If the 5th message above was also sent using plain-text format:</p>
-<pre class="prettyprint">id=1:2342
-registration_id=32
-</pre>
-
-
-<h2 id="app-server">Implementing an HTTP-Based App Server</h2>
-
-<p>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.</a>
-
-
-<p>Requirements</p>
-<p>For the web server:</p>
-<ul>
-  <li> <a href="http://ant.apache.org/">Ant 1.8</a> (it might work with earlier versions, but it's not guaranteed).</li>
-  <li>One of the following:
-    <ul>
-      <li>A running web server compatible with Servlets API version 2.5, such as
-<a href="http://tomcat.apache.org/">Tomcat 6</a> or <a href="http://jetty.codehaus.org/">Jetty</a>, or</li>
-      <li><a href="http://code.google.com/appengine/">Java App Engine SDK</a>
-version 1.6 or later.</li>
-    </ul>
-  </li>
-  <li>A Google account registered to use GCM.</li>
-  <li>The API  key for that account.</li>
-</ul>
-<p>For the Android application:</p>
-<ul>
-  <li>Emulator (or device) running Android 2.2 with Google APIs.</li>
-  <li>The Google API project number of the account registered to use GCM.</li>
-</ul>
-
-<h3 id="gcm-setup">Setting Up GCM</h3>
-<p>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 <a href="https://code.google.com/apis/console">
-Google API Console</a>.</p>
-<p>For instructions on how to set up GCM, see <a href="gs.html">Getting Started</a>.</p>
-
-
-<h3 id="server-setup">Setting Up an HTTP Server</h3>
-<p>This section describes the different options for setting up an HTTP server.</p>
-
-<h4 id="webserver-setup">Using a standard web server</h4>
-<p>To set up the server using a standard, servlet-compliant web server:</p>
-<ol>
-  <li>From the <a href="http://code.google.com/p/gcm">open source site</a>,
-download the following directories: <code>gcm-server</code>,
-<code>samples/gcm-demo-server</code>, and <code>samples/gcm-demo-appengine</code>.</p>
-
-
-  <li>In a text editor, edit the <code>samples/gcm-demo-server/WebContent/WEB-INF/classes/api.key</code> and replace the existing text with the API key obtained above.</li>
-  <li>In a shell window, go to the <code>samples/gcm-demo-server</code> directory.</li>
-  <li>Generate the server's WAR file by running <code>ant war</code>:</li>
-
-  <pre class="prettyprint">$ 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: <strong>dist/gcm-demo.war</strong>
-
-BUILD SUCCESSFUL
-Total time: 0 seconds
-</pre>
-
-  <li>Deploy the <code>dist/gcm-demo.war</code> to your running server. For instance, if you're using Jetty, copy <code>gcm-demo.war</code> to the <code>webapps</code> directory of the Jetty installation.</li>
-  <li>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 <code>http://192.168.1.10:8080/gcm-demo/home</code>, where <code>gcm-demo</code> is the application context and <code>/home</code> is the path of the main servlet.
-
-  </li>
-</ol>
-<p class="note"><strong>Note:</strong> You can get the IP by running <code>ifconfig</code> on Linux or MacOS, or <code>ipconfig</code> on Windows. </p>
-
-<p> You server is now ready.</p>
-
-<h4 id="appengine-setup">Using App Engine for Java</h4>
-
-<p>To set up the server using a standard App Engine for Java:</p>
-<ol>
-  <li>Get the files from the <a href="http://code.google.com/p/gcm">open source
-site</a>, as described above.</p>
-  </li>
-  <li>In a text editor, edit
-<code>samples/gcm-demo-appengine/src/com/google/android/gcm/demo/server/ApiKeyInitializer.java</code>
-and replace the existing text with the API key obtained above.
-
-  <p class="note"><strong>Note:</strong> 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 <code>Datastore Viewer</code> to change
-it later.</p>
-
-  </li>
-  <li>In a shell window, go to the <code>samples/gcm-demo-appengine</code> directory.</li>
-  <li>Start the development App Engine server by <code>ant runserver</code>,
-using the <code>-Dsdk.dir</code> to indicate the location of the App Engine SDK
-and <code>-Dserver.host</code> to set your server's hostname or IP address:</li>
-
-<pre class="prettyprint">
-$ 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
-</pre>
-
-  <li>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
-<code>http://192.168.1.10:8080/home</code>, where <code>/home</code>
-is the path of the main servlet.</li>
-
-  <p class="note"><strong>Note:</strong> You can get the IP by running <code>ifconfig</code>
-on Linux or MacOS, or <code>ipconfig</code> on Windows.</p>
-
-</ol>
-<p> You server is now ready.</p>
diff --git a/docs/html/google/gcm/notifications.jd b/docs/html/google/gcm/notifications.jd
index 43a7368..5171850 100644
--- a/docs/html/google/gcm/notifications.jd
+++ b/docs/html/google/gcm/notifications.jd
@@ -14,15 +14,14 @@
 <h2>In this document</h2>
 
 <ol class="toc">
-  <li><a href="#request">Request Format</a></li>
-  <li><a href="#create">Generate a Notification Key</a></li>
-  <li><a href="#add">Add Registration IDs</a></li>
-  <li><a href="#remove">Remove Registration IDs</a></li>
-  <li><a href="#upstream">Send Upstream Messages</a></li>
-  <li><a href="#response">Response Formats</a>
-    <ol class="toc">
-      <li><a href="#response-create">Create/add/remove operations</a>
-      <li><a href="#response-send">Send operations</a>
+  <li><a href="#what">What are User Notifications?</a> </li>
+  <li><a href="#examples">Examples</a>
+    <ol>
+      <li><a href="#create">Generate a notification key</a></li>
+      <li><a href="#add">Add registration IDs</a></li>
+      <li><a href="#remove">Remove registration IDs</a></li>
+      <li><a href="#upstream">Send upstream messages</a></li>
+      <li><a href="#response">Response formats</a></li>
     </ol>
   </li>
 </ol>
@@ -39,51 +38,32 @@
 
 <p class="note"><strong>Note:</strong> To try out this feature, sign up using <a href="https://services.google.com/fb/forms/gcm/">this form</a>.</p>
 
+<p>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 <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">GoogleCloudMessaging</a> APIs. To use upstream messaging and the new streamlined registration process, you must <a href="{@docRoot}google/play-services/setup.html">set up</a> the Google Play services SDK.</p>
 
-<p>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 <em>user notifications</em>. User notifications make it possible for every
-app instance that a user owns to reflect the latest messaging state. For example:</p>
+<h2 id="what">What are User Notifications?</h2>
+
+<p>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 <em>user notifications</em>. User notifications make it possible for every app instance that a user owns to reflect the latest messaging state. For example:</p>
 
   <ul>
-  <li>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.</li>
-
-  <li>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.</li>
-
-  <li>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.</li>
+  <li>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.</li>
+  <li>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.</li>
+  <li>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.</li>
 </ul>
 
-<p>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.</p>
+<p>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.</p>
 
-<p class="note"><strong>Note:</strong> 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.
+<p class="note"><strong>Note:</strong> 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.
 </p>
 
-<p>You can use this feature with either the <a href="ccs.html">XMPP</a> (CCS) or
-<a href="http.html">HTTP</a> connection server.</p>
+<p>You can use this feature with either the new <a href="ccs.html">GCM Cloud Connection Server</a> (CCS), or the older <a href="gcm.html">GCM HTTP server</a>.</p>
 
 
-<p>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.</p>
+<h3 id="examples">Examples</h3>
 
-<h2 id="request">Request Format</h2>
-<p>To send a  message, the application server issues a POST request to
-<code>https://android.googleapis.com/gcm/notification</code>.</p>
+<p>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.</p>
+
+<h4 id="request">Request format</h4>
+<p>To send a  message, the application server issues a POST request to <code>https://android.googleapis.com/gcm/notification</code>.</p>
 
 <p>Here is the HTTP request header you should use for all create/add/remove operations:</p>
 
@@ -92,22 +72,12 @@
 Header: "Authorization", "key=API_KEY"
 </pre>
 
-<h2 id="create">Generate a Notification Key</h2>
+<h4 id="create">Generate a notification key</h4>
 
-<p>This example shows how to create a new <code>notification_key</code> for a
-<code>notification_key_name</code> called <code>appUser-Chris</code>.
-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 <code>notification_key_name</code>
-and <code>notification_key</code> are unique to a group of registration IDs. It is also
-important that <code>notification_key_name</code> 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.</p>
+<p>This example shows how to create a new <code>notification_key</code> for a <code>notification_key_name</code> called <code>appUser-Chris</code>. 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 <code>notification_key_name</code> and <code>notification_key</code> are unique to a group of registration IDs. It is also important that <code>notification_key_name</code> 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.</p>
 
 
-<p>A create operation returns a token (<code>notification_key</code>). Third parties
-must save this token (as well as its mapping to the <code>notification_key_name</code>)
-to use in subsequent operations:</p>
+<p>A create operation returns a token (<code>notification_key</code>). Third parties must save this token (as well as its mapping to the <code>notification_key_name</code>) to use in subsequent operations:</p>
 
 <pre>request:
 { 
@@ -116,14 +86,11 @@
    &quot;registration_ids&quot;: [&quot;4&quot;, &quot;8&quot;, &quot;15&quot;, &quot;16&quot;, &quot;23&quot;, &quot;42&quot;]
 }</pre>
 
-<h2 id="add">Add Registration IDs</h2>
+<h4 id="add">Add registration IDs</h4>
 
-<p>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.</p>
+<p>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.</p>
 
-<p>Note that the <code>notification_key_name</code> is not strictly required for
-adding/removing regIDs. But including it protects you against accidentally using
-the incorrect <code>notification_key</code>.</p>
+<p>Note that the <code>notification_key_name</code> is not strictly required for adding/removing regIDs. But including it protects you against accidentally using the incorrect <code>notification_key</code>.</p>
 
 <pre>request:
 { 
@@ -133,7 +100,7 @@
    &quot;registration_ids&quot;: [&quot;4&quot;, &quot;8&quot;, &quot;15&quot;, &quot;16&quot;, &quot;23&quot;, &quot;42&quot;]
 }</pre>
 
-<h2 id="remove">Remove Registration IDs</h2>
+<h4 id="remove">Remove registration IDs</h4>
 
 <p>This example shows how to remove registration IDs for a given notification key:</p>
 <pre>request:
@@ -144,14 +111,9 @@
    &quot;registration_ids&quot;: [&quot;4&quot;, &quot;8&quot;, &quot;15&quot;, &quot;16&quot;, &quot;23&quot;, &quot;42&quot;]
 }</pre>
 
-<h2 id="upstream">Send Upstream Messages</h2>
+<h4 id="upstream">Send upstream messages</h4>
 
-<p>To send an upstream (device-to-cloud) message, you must use the
-<a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
-{@code GoogleCloudMessaging}</a> 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&mdash;for example, to dismiss a notification.
-Here is an example that shows targeting a {@code notification_key}:</p>
+<p>To send an upstream (device-to-cloud) message, you must use the <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">GoogleCloudMessaging</a> 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&mdash;for example, to dismiss a notification. Here is an example that shows targeting a {@code notification_key}:</p>
 
 <pre>GoogleCloudMessaging gcm = GoogleCloudMessaging.get(context);
 String to = NOTIFICATION_KEY;
@@ -163,21 +125,17 @@
 gcm.send(to, id, data);
 </pre>
 
-<p>This call generates the necessary XMPP stanza for sending the message. The
-Bundle data consists of a key-value pair.</p>
+<p>This call generates the necessary XMPP stanza for sending the message. The Bundle data consists of a key-value pair.</p>
 
-<p>For a complete example, see <a href="client.html">Implementing GCM Client</a>.
+<p>For a complete example, see <a href="gs.html#gs_example">Getting Started</a>. 
 
-<h2 id="response">Response Formats</h2>
+<h4 id="response">Response formats</h4>
 
-<p>This section shows examples of the responses that can be returned for
-notification key operations.</p>
+<p>This section shows examples of the responses that can be returned for notification key operations.</p>
 
-<h3 id="response-create">Create/add/remove operations</h3>
+<h5>Response for create/add/remove operations</h5>
 
-<p>When you make a request to create a {@code notification_key} or to add/remove its
-regIDs, a successful response always returns the <code>notification_key</code>.
-his is the {@code notification_key} you will use for sending messages:</p>
+<p>When you make a request to create a {@code notification_key} or to add/remove its the wayregIDs, a successful response always returns the <code>notification_key</code>. This is the {@code notification_key} you will use for sending messages:</p>
 
 <pre>HTTP status: 200
 { 
@@ -185,23 +143,18 @@
 }</pre>
 
 
-<h3 id="response-send">Send operations</h3>
+<h5>Response for send operations</h5>
 
-<p>For a send operation that has a {@code notification_key} as its target, the
-possible responses are success, partial success, and failure.</p>
+<p>For a send operation that has a {@code notification_key} as its target, the possible responses are success, partial success, and failure.</p>
 
-<p>Here is an example of "success"&mdash;the {@code notification_key} has 2 regIDs
-associated with it, and the message was successfully sent to both of them:</p>
+<p>Here is an example of "success"&mdash;the {@code notification_key} has 2 regIDs associated with it, and the message was successfully sent to both of them:</p>
 
 <pre>{
   "success": 2,
   "failure": 0
 }</pre>
 
-<p>Here is an example of "partial success"&mdash;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:</p>
+<p>Here is an example of "partial success"&mdash;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:</p>
 
 <pre>{
   "success":1,
@@ -212,9 +165,7 @@
   ]
 }</pre>
 
-<p>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.</p>
+<p>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.</p>
 
 
 
diff --git a/docs/html/google/gcm/server.jd b/docs/html/google/gcm/server.jd
index b5e6b48..92a1531 100644
--- a/docs/html/google/gcm/server.jd
+++ b/docs/html/google/gcm/server.jd
@@ -1,34 +1,36 @@
-page.title=Implementing GCM Server
+page.title=GCM Server
 @jd:body
 
 <div id="qv-wrapper">
 <div id="qv">
 
+<h2>Quickview</h2>
+
+<ul>
+<li>Understand how to set up the server side of a GCM app.</li>
+<li>Become familiar with the <a href="{@docRoot}reference/com/google/android/gcm/server/package-summary.html">GCM server helper library</a>.</li>
+</ul>
+
+
 <h2>In this document</h2>
 
-<ol class="toc">
-  <li><a href="#choose">Choosing a GCM Connection Server</a></li>
-  <li><a href="#role">Role of the 3rd-party Application Server</a></li>
-    <li><a href="#send-msg">Sending Messages</a>
-    <ol class="toc">
-
-      <li><a href="#target">Target</a></li>
-      <li><a href="#payload">Payload</a></li>
-      <li><a href="#params">Message parameters</a>
+<ol>
+  <li><a href="#requirements">Requirements</a> </li>
+  <li><a href="#gcm-setup">Setting Up GCM</a></li>
+  <li><a href="#server-setup">Setting Up an HTTP Server</a>
+    <ol>
+      <li><a href="#webserver-setup">Using a standard web server</a></li>
+      <li><a href="#appengine-setup">Using App Engine for Java</a></li>
     </ol>
-    </li>
-  <li><a href="#receive">Receiving Messages</a> </li>
   </li>
-
 </ol>
 
 <h2>See Also</h2>
 
 <ol class="toc">
 <li><a href="gs.html">Getting Started</a></li>
-<li><a href="client.html">Implementing GCM Client</a></li>
-<li><a href="ccs.html">Cloud Connection Server (XMPP)</a></li>
-<li><a href="http.html">HTTP Connection Server</a></li>
+<li><a href="client.html">GCM Client</a></li>
+<li><a href="ccs.html">Cloud Connection Server</a></li>
 
 
 </ol>
@@ -37,342 +39,122 @@
 </div>
 
 
-<p>The server side of GCM consists of 2 components:</p>
+
+
+<p>This document gives examples of GCM server-side code for HTTP. For an example of an XMPP server (<a href="ccs.html">Cloud Connection Server</a>), see <a href="gs.html#server">Getting Started</a>. 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 <a href="gs.html">Getting Started</a>.</a>
+
+<h2 id="requirements">Requirements</h2>
+<p>For the web server:</p>
 <ul>
-<li>Google-provided <strong>GCM Connection Servers</strong>
-take messages from a 3rd-party application server and send them to a GCM-enabled
-Android application (the &quot;client app&quot;) running on a device. For example,
-Google provides connection servers for <a href="{@docRoot}google/gcm/http.html">
-HTTP</a> and <a href="{@docRoot}google/gcm/ccs.html">CCS</a> (XMPP).</li>
-<li>A <strong>3rd-party application server</strong> that you must implement. This application
-server sends data to a GCM-enabled Android application via the chosen GCM connection server.</li>
-</ul>
-</p>
-
-<p>Here are the basic steps you follow to implement your 3rd-party app server:</p>
-
-<ul>
-      <li>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 <a href="#choose">
-      Choosing a GCM Connection Server</a>.</li>
-      <li>Decide how you want to implement your app server. For example:
-        <ul>
-          <li>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.</li>
-          <li>If you decide to use the XMPP connection server, you can use
-the provided Python or Java <a href="http://www.igniterealtime.org/projects/smack/">
-Smack</a> demo apps as a starting point.</li>
-        <li>Note that Google AppEngine does not support connections to CCS.</li>
-        </ul>
-      </li>
-    </ul>
-  </li>
-</ul>
-
-<p>A full GCM implementation requires both a client implementation and a server
-implementation. For more
-information about implementing the client side, see <a href="client.html">
-Implementing GCM Client</a>.</p>
-
-<h2 id="choose">Choosing a GCM Connection Server</h2>
-
-<p>Currently GCM provides two connection servers: <a href="{@docRoot}google/gcm/http.html">
-HTTP</a> and <a href="{@docRoot}google/gcm/ccs.html">CCS</a> (XMPP). You can use them
-separately or in tandem. CCS messaging differs from GCM HTTP messaging in the following ways:</p>
-<ul>
-  <li>Upstream/Downstream messages
+  <li> <a href="http://ant.apache.org/">Ant 1.8</a> (it might work with earlier versions, but it's not guaranteed).</li>
+  <li>One of the following:
     <ul>
-      <li>GCM HTTP: Downstream only: cloud-to-device. </li>
-      <li>CCS: Upstream and downstream (device-to-cloud, cloud-to-device). </li>
+      <li>A running web server compatible with Servlets API version 2.5, such as <a href="http://tomcat.apache.org/">Tomcat 6</a> or <a href="http://jetty.codehaus.org/">Jetty</a>, or</li>
+      <li><a href="http://code.google.com/appengine/">Java App Engine SDK</a> version 1.6 or later.</li>
     </ul>
   </li>
-  <li>Asynchronous messaging
-    <ul>
-      <li>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.</li>
-      <li>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.</li>
-    </ul>
-  </li>
-
-  <li>JSON
-    <ul>
-      <li>GCM HTTP: JSON messages sent as HTTP POST.</li>
-      <li>CCS: JSON messages encapsulated in XMPP messages.</li>
-    </ul>
-  </li>
+  <li>A Google account registered to use GCM.</li>
+  <li>The API  key for that account.</li>
 </ul>
-
-<h2 id="role">Role of the 3rd-party Application Server</h2>
-
-<p>Before you can write client Android applications that use the GCM feature, you must
-have an  application server that meets the following criteria:</p>
-
+<p>For the Android application:</p>
 <ul>
-  <li>Able to communicate with your client.</li>
-  <li>Able to  fire off properly formatted requests to the GCM server.</li>
-  <li>Able to handle requests and resend them as needed, using
-<a href="http://en.wikipedia.org/wiki/Exponential_backoff">exponential back-off.</a></li>
-  <li>Able to store the API key and client registration IDs. The
-API key is included in the header of POST requests that send
-messages.</li>
- <li>Able to store the API key and client registration IDs.</li>
- <li>Able to generate message IDs to uniquely identify each message it sends.</li>
+  <li>Emulator (or device) running Android 2.2 with Google APIs.</li>
+  <li>The Google API project number of the account registered to use GCM.</li>
 </ul>
+<h2 id="gcm-setup">Setting Up GCM</h2>
+<p>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 <a href="https://code.google.com/apis/console">Google API Console</a>.</p>
+<p>For instructions on how to set up GCM, see <a href="gs.html">Getting Started</a>.</p>
 
-<h2 id="send-msg">Sending Messages</h2>
 
-<p>Here is the general sequence of events that occurs when a 3rd-party application
-server sends a message:</p>
+<h2 id="server-setup">Setting Up an HTTP Server</h2>
+<p>This section describes the different options for setting up an HTTP server.</p>
+<h3 id="webserver-setup">Using a standard web server</h3>
+<p>To set up the server using a standard, servlet-compliant web server:</p>
 <ol>
-  <li>The application server sends a message to GCM servers.</li>
-  <li>Google enqueues and stores the message in case the device is offline.</li>
-  <li>When the device is online, Google sends the message to the device.</li>
-  <li>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.</li>
-  <li>The Android application processes the message. </li>
+  <li>From the <a href="http://code.google.com/p/gcm">open source site</a>, download the following directories: <code>gcm-server</code>, <code>samples/gcm-demo-server</code>, and <code>samples/gcm-demo-appengine</code>.</p>
+
+
+  <li>In a text editor, edit the <code>samples/gcm-demo-server/WebContent/WEB-INF/classes/api.key</code> and replace the existing text with the API key obtained above.</li>
+  <li>In a shell window, go to the <code>samples/gcm-demo-server</code> directory.</li>
+  <li>Generate the server's WAR file by running <code>ant war</code>:</li>
+  
+  <pre class="prettyprint">$ 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: <strong>dist/gcm-demo.war</strong>
+
+BUILD SUCCESSFUL
+Total time: 0 seconds
+</pre>
+  
+  <li>Deploy the <code>dist/gcm-demo.war</code> to your running server. For instance, if you're using Jetty, copy <code>gcm-demo.war</code> to the <code>webapps</code> directory of the Jetty installation.</li>
+  <li>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 <code>http://192.168.1.10:8080/gcm-demo/home</code>, where <code>gcm-demo</code> is the application context and <code>/home</code> is the path of the main servlet.
+    
+  </li>
 </ol>
+<p class="note"><strong>Note:</strong> You can get the IP by running <code>ifconfig</code> on Linux or MacOS, or <code>ipconfig</code> on Windows. </p>
 
-<p>The following sections describe the basic requirements for
-sending messages.</p>
+<p> You server is now ready.</p>
 
-<h3 id="target">Target</h3>
-<p>Required. When your app server sends a message in GCM, it must specify a target.</p>
-<p>For HTTP you must specify the target as one of:</p>
-<ul>
-<li><code>registration_ids</code>: 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.</li>
-<li><code>notification_key</code>: For sending to multiple devices owned by a single user.</li>
-</ul>
-<p>For CCS (XMPP):</p>
-<ul>
-<li>You must specify the target as the &quot;to&quot; field, where the &quot;to&quot;
-field may contain a single registration ID or a notification key.
-CCS does not support multicast messaging.</li>
-</ul>
-<h3 id="payload">Payload</h3>
-<p>Optional. If you are including a payload in the message, you use the <code>data</code>
-parameter to include the payload. This applies for both HTTP and CCS.</p>
+<h3 id="appengine-setup">Using App Engine for Java</h3>
 
-<h3 id="params">Message parameters</h3>
-
-<p>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.</p>
-
-<p class="table-caption" id="table1">
-  <strong>Table 1.</strong> Message parameters.</p>
-
-<table>
-  <tr>
-    <th>Field</th>
-    <th>Description</th>
-<th>Where Supported</th>
-</tr>
-  <td><code>to</code></td>
-<td>In CCS, used in place of <code>registration_ids</code> to specify the
-recipient of a message. Its value must be a registration ID.
-The value is a string. Required.</td>
-<td>CCS</td>
-</tr>
-<tr>
-<td><code>message_id</code></td>
-<td>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.</td>
-<td>CCS</td>
-</tr>
-<tr>
-<td><code>message_type</code></td>
-<td>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
-<a href="ccs.html">Cloud Connection Server</a>. The value is a string. Optional.</td>
-<td>CCS</td>
-<tr>
-  <td><code>registration_ids</code></td>
-  <td>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&mdash;this can be either a
-registration ID, an array of registration IDs, or a {@code notification_key}.
-Required.</td>
-<td>HTTP</td>
-</tr>
- <tr>
-    <td><code>notification_key</code></td>
-    <td>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 <a href="notifications.html">User Notifications</a>. Optional.
-</td>
-<td style="width:100px">HTTP. This feature is supported in CCS, but you use it by
-specifying a notification key in the &quot;to&quot; field.</td>
-</tr>
-  <tr>
-    <td><code>collapse_key</code></td>
-    <td>An arbitrary string (such as &quot;Updates Available&quot;) 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 &quot;last&quot; message may not actually be the last
-message sent by the application server. Collapse keys are also called
-<a href="#s2s">send-to-sync messages</a>.
-<br>
-<strong>Note:</strong> 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 <a href="adv.html#collapsible">Advanced Topics</a> for more
-discussion of this topic. Optional.</td>
-<td>CCS, HTTP</td>
-</tr>
-  <tr>
-    <td><code>data</code></td>
-    <td>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, <code>"data":{"score":"3x1"}</code> would result in an intent extra
-named <code>score</code> whose value is the string <code>3x1</code>.
-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 (<code>from</code> or any word
-starting with <code>google.</code>). To complicate things slightly, there are
-some reserved words (such as <code>collapse_key</code>) 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.</td>
-<td>CCS, HTTP</td>
-</tr>
-  <tr>
-    <td><code>delay_while_idle</code></td>
-    <td>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 <code>collapse_key</code> value will be
-sent. The default value is <code>false</code>, and must be a JSON boolean. Optional.</td>
-<td>CCS, HTTP</td>
-</tr>
-  <tr>
-    <td><code>time_to_live</code></td>
-    <td>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).</td>
-<td>CCS, HTTP</td>
-</tr>
-<tr>
-  <td><code>restricted_package_name</code></td>
-  <td>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.
-  </td>
-<td>HTTP</td>
-</tr>
-<tr>
-  <td><code>dry_run</code></td>
-  <td>If included, allows developers to test their request without actually
-sending a message. Optional. The default value is <code>false</code>, and must
-be a JSON boolean.
-  </td>
-<td>HTTP</td>
-</tr>
-</table>
-
-<p>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
-<code>dry_run</code> with the value <code>true</code>. 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.</p>
-
-<h3 id="plain-text">Plain text (HTTP only)</h3>
-
-<p>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:
-<table>
-  <tr>
-    <th>Field</th>
-    <th>Description</th>
-  </tr>
-  <tr>
-    <td><code>registration_id</code></td>
-    <td>Must contain the registration ID of the single device receiving the message.
-Required.</td>
-  </tr>
-  <tr>
-    <td><code>collapse_key</code></td>
-    <td>Same as JSON (see previous table). Optional.</td>
-  </tr>
-  <tr>
-    <td><code>data.&lt;key&gt;</code></td>
-
-    <td>Payload data, expressed as parameters prefixed with <code>data.</code> and
-suffixed as the key. For instance, a parameter of <code>data.score=3x1</code> would
-result in an intent extra named <code>score</code> whose value is the string
-<code>3x1</code>. 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 (<code>from</code> or any word starting with
-<code>google.</code>). To complicate things slightly, there are some reserved words
-(such as <code>collapse_key</code>) 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.</td>
-
-  </tr>
-  <tr>
-    <td><code>delay_while_idle</code></td>
-    <td>Should be represented as <code>1</code> or <code>true</code> for
-<code>true</code>, anything else for <code>false</code>. Optional. The default
-value is <code>false</code>.</td>
-  </tr>
-  <tr>
-    <td><code>time_to_live</code></td>
-    <td>Same as JSON (see previous table). Optional.</td>
-  </tr>
-<tr>
-  <td><code>restricted_package_name</code></td>
-  <td>Same as JSON (see previous table). Optional.
-  </td>
-</tr>
-<tr>
-  <td><code>dry_run</code></td>
-  <td>Same as JSON (see previous table). Optional.
-  </td>
-</tr>
-</table>
-
-<h2 id="receive">Receiving Messages</h2>
-
-<p>This is the sequence of events that occurs when an Android application
-installed on a mobile device receives a message:</p>
-
+<p>To set up the server using a standard App Engine for Java:</p>
 <ol>
-  <li>The system receives the incoming message and extracts the raw key/value
-pairs from the message payload, if any.</li>
-  <li>The system passes the key/value pairs to the targeted Android application
-in a <code>com.google.android.c2dm.intent.RECEIVE</code> Intent as a set of
-extras.</li>
-  <li>The Android application extracts the raw data
-from the <code>com.google.android.c2dm.intent.RECEIVE</code><code> </code>Intent
-by key and processes the data.</li>
-</ol>
+  <li>Get the files from the <a href="http://code.google.com/p/gcm">open source site</a>, as described above.</p>
+  </li>
+  <li>In a text editor, edit <code>samples/gcm-demo-appengine/src/com/google/android/gcm/demo/server/ApiKeyInitializer.java</code> and replace the existing text with the API key obtained above.
 
-<p>See the documentation for each connection server for more detail on how it
-handles responses.</p>
+  <p class="note"><strong>Note:</strong> 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 <code>Datastore Viewer</code> to change it later.</p>
+  
+  </li>
+  <li>In a shell window, go to the <code>samples/gcm-demo-appengine</code> directory.</li>
+  <li>Start the development App Engine server by <code>ant runserver</code>, using the <code>-Dsdk.dir</code> to indicate the location of the App Engine SDK and <code>-Dserver.host</code> to set your server's hostname or IP address:</li>
+
+<pre class="prettyprint">
+$ 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
+</pre>
+
+  <li>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 <code>http://192.168.1.10:8080/home</code>, where <code>/home</code> is the path of the main servlet.</li>
+  
+  <p class="note"><strong>Note:</strong> You can get the IP by running <code>ifconfig</code> on Linux or MacOS, or <code>ipconfig</code> on Windows.</p>
+  
+</ol>
+<p> You server is now ready.</p>
+
+
diff --git a/docs/html/google/google_toc.cs b/docs/html/google/google_toc.cs
index 7f76c12..999c44e 100644
--- a/docs/html/google/google_toc.cs
+++ b/docs/html/google/google_toc.cs
@@ -123,27 +123,24 @@
         <span class="en">Google Cloud Messaging</span></a>
       </div>
       <ul>
-        <li><a href="<?cs var:toroot?>google/gcm/gcm.html">
-            <span class="en">Overview</span></a>
-        </li>
         <li><a href="<?cs var:toroot?>google/gcm/gs.html">
             <span class="en">Getting Started</span></a>
         </li>
-        <li><a href="<?cs var:toroot?>google/gcm/client.html">
-            <span class="en">Implementing GCM Client</span></a>
+        <li><a href="<?cs var:toroot?>google/gcm/gcm.html">
+            <span class="en">Architectural Overview</span></a>
         </li>
-        <li class="nav-section"><div class="nav-section-header"><a href="<?cs var:toroot?>google/gcm/server.html">
-              <span class="en">Implementing GCM Server</span></a></div>
-              <ul>
-              <li><a href="<?cs var:toroot?>google/gcm/ccs.html">
-              <span class="en">CCS (XMPP)</span></a></li>
-              <li><a href="<?cs var:toroot?>google/gcm/http.html">
-              <span class="en">HTTP</span></a></li>
-              </ul>
+         <li><a href="<?cs var:toroot?>google/gcm/ccs.html">
+              <span class="en">Cloud Connection Server</span></a>
         </li>
         <li><a href="<?cs var:toroot?>google/gcm/notifications.html">
               <span class="en">User Notifications</span></a>
         </li>
+        <li><a href="<?cs var:toroot?>google/gcm/client.html">
+            <span class="en">GCM Client</span></a>
+        </li>
+        <li><a href="<?cs var:toroot?>google/gcm/server.html">
+            <span class="en">GCM Server</span></a>
+        </li>
         <li><a href="<?cs var:toroot?>google/gcm/adv.html">
             <span class="en">Advanced Topics</span></a>
         </li>
diff --git a/docs/html/images/gcm/CCS-ack.png b/docs/html/images/gcm/CCS-ack.png
deleted file mode 100644
index bce2ab2..0000000
--- a/docs/html/images/gcm/CCS-ack.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/images/gcm/GCM-arch.png b/docs/html/images/gcm/GCM-arch.png
deleted file mode 100644
index e8ffb15..0000000
--- a/docs/html/images/gcm/GCM-arch.png
+++ /dev/null
Binary files differ
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 55664fd..47cf875 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -559,6 +559,7 @@
      * @param dstHeight The new bitmap's desired height.
      * @param filter    true if the source should be filtered.
      * @return The new scaled bitmap or the source bitmap if no scaling is required.
+     * @throws IllegalArgumentException if width is <= 0, or height is <= 0
      */
     public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight,
             boolean filter) {
@@ -611,6 +612,9 @@
      * @param width    The number of pixels in each row
      * @param height   The number of rows
      * @return A copy of a subset of the source bitmap or the source bitmap itself.
+     * @throws IllegalArgumentException if the x, y, width, height values are
+     *         outside of the dimensions of the source bitmap, or width is <= 0,
+     *         or height is <= 0
      */
     public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
         return createBitmap(source, x, y, width, height, null, false);
@@ -637,7 +641,8 @@
      *                   translation.
      * @return A bitmap that represents the specified subset of source
      * @throws IllegalArgumentException if the x, y, width, height values are
-     *         outside of the dimensions of the source bitmap.
+     *         outside of the dimensions of the source bitmap, or width is <= 0,
+     *         or height is <= 0
      */
     public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
             Matrix m, boolean filter) {
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java
index 6f71a2b..9e07bd4 100644
--- a/graphics/java/android/graphics/Camera.java
+++ b/graphics/java/android/graphics/Camera.java
@@ -22,6 +22,8 @@
  * {@link Canvas}.
  */
 public class Camera {
+    private Matrix mMatrix;
+
     /**
      * Creates a new camera, with empty transformations.
      */
@@ -147,7 +149,13 @@
      * @param canvas The Canvas to set the transform matrix onto
      */
     public void applyToCanvas(Canvas canvas) {
-        nativeApplyToCanvas(canvas.mNativeCanvas);
+        if (canvas.isHardwareAccelerated()) {
+            if (mMatrix == null) mMatrix = new Matrix();
+            getMatrix(mMatrix);
+            canvas.concat(mMatrix);
+        } else {
+            nativeApplyToCanvas(canvas.mNativeCanvas);
+        }
     }
 
     public native float dotWithNormal(float dx, float dy, float dz);
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index 4e06448..32e0c01 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -21,9 +21,6 @@
 
 /**
  * The Matrix class holds a 3x3 matrix for transforming coordinates.
- * Matrix does not have a constructor, so it must be explicitly initialized
- * using either reset() - to construct an identity matrix, or one of the set..()
- * functions (e.g. setTranslate, setRotate, etc.).
  */
 public class Matrix {
 
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 1700473..cb6bb2e 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -234,6 +234,7 @@
     Vector<CacheTexture*>* cacheTextures = NULL;
     switch (format) {
         case SkMask::kA8_Format:
+        case SkMask::kBW_Format:
             cacheTextures = &mACacheTextures;
             break;
         case SkMask::kARGB32_Format:
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index 55503ce..cbed3e4 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -233,9 +233,11 @@
 bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
     switch (glyph.fMaskFormat) {
         case SkMask::kA8_Format:
+        case SkMask::kBW_Format:
             if (mFormat != GL_ALPHA) {
 #if DEBUG_FONT_RENDERER
-                ALOGD("fitBitmap: kA8_Format glyph cannot fit into texture format %x", mFormat);
+                ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs",
+                        mFormat);
 #endif
                 return false;
             }
@@ -243,7 +245,7 @@
         case SkMask::kARGB32_Format:
             if (mFormat != GL_RGBA) {
 #if DEBUG_FONT_RENDERER
-                ALOGD("fitBitmap: kARGB32_Format glyph cannot fit into texture format %x", mFormat);
+                ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat);
 #endif
                 return false;
             }
diff --git a/location/java/android/location/IFusedGeofenceHardware.aidl b/location/java/android/location/IFusedGeofenceHardware.aidl
index 9dbf1f4..d8c3585 100644
--- a/location/java/android/location/IFusedGeofenceHardware.aidl
+++ b/location/java/android/location/IFusedGeofenceHardware.aidl
@@ -16,8 +16,8 @@
  
 package android.location;
 
-import android.location.Geofence;
- 
+import android.hardware.location.GeofenceHardwareRequestParcelable;
+
 /**
  * Fused Geofence Hardware interface.
  *
@@ -39,11 +39,9 @@
     /**
      * Adds a given list of geofences to the system.
      *
-     * @param geofenceIdsArray    The list of geofence Ids to add.
-     * @param geofencesArray      the list of geofences to add.
+     * @param geofenceRequestsArray    The list of geofences to add.
      */
-    // TODO: [GeofenceIntegration] GeofenceHardwareRequest is not a parcelable class exposed in aidl
-    void addGeofences(in int[] geofenceIdsArray, in Geofence[] geofencesArray);
+    void addGeofences(in GeofenceHardwareRequestParcelable[] geofenceRequestsArray);
 
     /**
      * Removes a give list of geofences from the system.
@@ -79,7 +77,8 @@
      *                                      the geofence.
      * @param monitorTransitions            The set of transitions to monitor.
      * @param notificationResponsiveness    The notification responsivness needed.
-     * @param unknownTimer                  The time span associated with the
+     * @param unknownTimer                  The time span associated with the.
+     * @param sourcesToUse                  The source technologies to use.
      *
      * Remarks: keep the options as separate fields to be able to leverage the class
      * GeofenceHardwareRequest without any changes
@@ -89,5 +88,6 @@
             in int lastTransition,
             in int monitorTransitions,
             in int notificationResponsiveness,
-            in int unknownTimer);
+            in int unknownTimer,
+            in int sourcesToUse);
 }
diff --git a/location/java/android/location/SettingInjectorService.java b/location/java/android/location/SettingInjectorService.java
new file mode 100644
index 0000000..d410408
--- /dev/null
+++ b/location/java/android/location/SettingInjectorService.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.preference.Preference;
+import android.util.Log;
+
+/**
+ * Dynamically specifies the summary (subtile) and enabled status of a preference injected into
+ * the "Settings > Location > Location services" list.
+ *
+ * The location services list is intended for use only by preferences that affect multiple apps from
+ * the same developer. Location settings that apply only to one app should be shown within that app,
+ * rather than in the system settings.
+ *
+ * To add a preference to the list, a subclass of {@link SettingInjectorService} must be declared in
+ * the manifest as so:
+ * <pre>
+ *     <service android:name="com.example.android.injector.MyInjectorService" >
+ *         <intent-filter>
+ *             <action android:name="com.android.settings.InjectedLocationSetting" />
+ *         </intent-filter>
+ *
+ *         <meta-data
+ *             android:name="com.android.settings.InjectedLocationSetting"
+ *             android:resource="@xml/my_injected_location_setting" />
+ *     </service>
+ * </pre>
+ * The resource file specifies the static data for the setting:
+ * <pre>
+ *     <injected-location-setting xmlns:android="http://schemas.android.com/apk/res/android"
+ *         android:label="@string/injected_setting_label"
+ *         android:icon="@drawable/ic_launcher"
+ *         android:settingsActivity="com.example.android.injector.MySettingActivity"
+ *     />
+ * </pre>
+ * Here:
+ * <ul>
+ *     <li>label: The {@link Preference#getTitle()} value. The title should make it clear which apps
+ *     are affected by the setting, typically by including the name of the developer. For example,
+ *     "Acme Corp. ads preferences." </li>
+ *
+ *     <li>icon: The {@link Preference#getIcon()} value. Typically this will be a generic icon for
+ *     the developer rather than the icon for an individual app.</li>
+ *
+ *     <li>settingsActivity: the activity which is launched to allow the user to modify the setting
+ *     value  The activity must be in the same package as the subclass of
+ *     {@link SettingInjectorService}. The activity should use your own branding to help emphasize
+ *     to the user that it is not part of the system settings.</li>
+ * </ul>
+ *
+ * For consistency, the label and {@link #getStatus()} values should be provided in all of the
+ * locales supported by the system settings app. The text should not contain offensive language.
+ *
+ * For compactness, only one copy of a given setting should be injected. If each account has a
+ * distinct value for the setting, then the {@link #getStatus()} value should represent a summary of
+ * the state across all of the accounts and {@code settingsActivity} should display the value for
+ * each account.
+ *
+ * Apps that violate these guidelines will be taken down from the Google Play Store and/or flagged
+ * as malware.
+ */
+// TODO: is there a public list of supported locales?
+// TODO: is there a public list of guidelines for settings text?
+public abstract class SettingInjectorService extends IntentService {
+
+    /**
+     * Name of the bundle key for the string specifying the status of the setting (e.g., "ON" or
+     * "OFF").
+     *
+     * @hide
+     */
+    public static final String STATUS_KEY = "status";
+
+    /**
+     * Name of the bundle key for the string specifying whether the setting is currently enabled.
+     *
+     * @hide
+     */
+    public static final String ENABLED_KEY = "enabled";
+
+    /**
+     * Name of the intent key used to specify the messenger
+     *
+     * @hide
+     */
+    public static final String MESSENGER_KEY = "messenger";
+
+    private final String mLogTag;
+
+    /**
+     * Constructor.
+     *
+     * @param logTag used for logging, must be less than 23 characters
+     */
+    public SettingInjectorService(String logTag) {
+        super(logTag);
+
+        // Fast fail if log tag is too long
+        Log.isLoggable(logTag, Log.WARN);
+
+        mLogTag = logTag;
+    }
+
+    @Override
+    final protected void onHandleIntent(Intent intent) {
+        // Get messenger first to ensure intent doesn't get messed with (in case we later decide
+        // to pass intent into getStatus())
+        Messenger messenger = intent.getParcelableExtra(MESSENGER_KEY);
+
+        Status status = getStatus();
+
+        // Send the status back to the caller via the messenger
+        Message message = Message.obtain();
+        Bundle bundle = new Bundle();
+        bundle.putString(STATUS_KEY, status.summary);
+        bundle.putBoolean(ENABLED_KEY, status.enabled);
+        message.setData(bundle);
+
+        if (Log.isLoggable(mLogTag, Log.DEBUG)) {
+            Log.d(mLogTag,
+                    "received " + intent + " and " + status + ", sending message: " + message);
+        }
+        try {
+            messenger.send(message);
+        } catch (RemoteException e) {
+            Log.e(mLogTag, "", e);
+        }
+    }
+
+    /**
+     * Reads the status of the setting.
+     */
+    protected abstract Status getStatus();
+
+    /**
+     * Dynamic characteristics of an injected location setting.
+     */
+    public static final class Status {
+
+        public final String summary;
+
+        public final boolean enabled;
+
+        /**
+         * Constructor.
+         *
+         * @param summary the {@link Preference#getSummary()} value
+         * @param enabled the {@link Preference#isEnabled()} value
+         */
+        public Status(String summary, boolean enabled) {
+            this.summary = summary;
+            this.enabled = enabled;
+        }
+
+        @Override
+        public String toString() {
+            return "Status{summary='" + summary + '\'' + ", enabled=" + enabled + '}';
+        }
+    }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 703eb27..f4e867e 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -564,6 +564,52 @@
     public native final String getName();
 
     /**
+     * Change a video encoder's target bitrate on the fly. The value is an
+     * Integer object containing the new bitrate in bps.
+     */
+    public static final String PARAMETER_KEY_VIDEO_BITRATE = "videoBitrate";
+
+    /**
+     * Temporarily suspend/resume encoding of input data. While suspended
+     * input data is effectively discarded instead of being fed into the
+     * encoder. This parameter really only makes sense to use with an encoder
+     * in "surface-input" mode, as the client code has no control over the
+     * input-side of the encoder in that case.
+     * The value is an Integer object containing the value 1 to suspend
+     * or the value 0 to resume.
+     */
+    public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames";
+
+    /**
+     * Request that the encoder produce a sync frame "soon".
+     * Provide an Integer with the value 0.
+     */
+    public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
+
+    /**
+     * Communicate additional parameter changes to the component instance.
+     */
+    public final void setParameters(Map<String, Object> params) {
+        if (params == null) {
+            return;
+        }
+
+        String[] keys = new String[params.size()];
+        Object[] values = new Object[params.size()];
+
+        int i = 0;
+        for (Map.Entry<String, Object> entry: params.entrySet()) {
+            keys[i] = entry.getKey();
+            values[i] = entry.getValue();
+            ++i;
+        }
+
+        setParameters(keys, values);
+    }
+
+    private native final void setParameters(String[] keys, Object[] values);
+
+    /**
      * Get the codec info. If the codec was created by createDecoderByType
      * or createEncoderByType, what component is chosen is not known beforehand,
      * and thus the caller does not have the MediaCodecInfo.
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index aeed7d4..90c12c6 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -72,7 +72,8 @@
     /**
      * Encapsulates the capabilities of a given codec component.
      * For example, what profile/level combinations it supports and what colorspaces
-     * it is capable of providing the decoded data in.
+     * it is capable of providing the decoded data in, as well as some
+     * codec-type specific capability flags.
      * <p>You can get an instance for a given {@link MediaCodecInfo} object with
      * {@link MediaCodecInfo#getCapabilitiesForType getCapabilitiesForType()}, passing a MIME type.
      */
@@ -139,6 +140,24 @@
          * OMX_COLOR_FORMATTYPE.
          */
         public int[] colorFormats;
+
+        private final static int FLAG_SupportsAdaptivePlayback       = (1 << 0);
+        private int flags;
+
+        /**
+         * <b>video decoder only</b>: codec supports seamless resolution changes.
+         */
+        public final static String FEATURE_AdaptivePlayback       = "adaptive-playback";
+
+        /**
+         * Query codec feature capabilities.
+         */
+        public final boolean isFeatureSupported(String name) {
+            if (name.equals(FEATURE_AdaptivePlayback)) {
+                return (flags & FLAG_SupportsAdaptivePlayback) != 0;
+            }
+            return false;
+        }
     };
 
     /**
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8689e19..a859506 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -38,6 +38,8 @@
 #include <media/stagefright/foundation/AString.h>
 #include <media/stagefright/MediaErrors.h>
 
+#include <nativehelper/ScopedLocalRef.h>
+
 #include <system/window.h>
 
 namespace android {
@@ -86,7 +88,7 @@
     mLooper->start(
             false,      // runOnCallingThread
             false,       // canCallJava
-            PRIORITY_DEFAULT);
+            PRIORITY_FOREGROUND);
 
     if (nameIsType) {
         mCodec = MediaCodec::CreateByType(mLooper, name, encoder);
@@ -186,9 +188,10 @@
         return err;
     }
 
-    jclass clazz = env->FindClass("android/media/MediaCodec$BufferInfo");
+    ScopedLocalRef<jclass> clazz(
+            env, env->FindClass("android/media/MediaCodec$BufferInfo"));
 
-    jmethodID method = env->GetMethodID(clazz, "set", "(IIJI)V");
+    jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
     env->CallVoidMethod(bufferInfo, method, offset, size, timeUs, flags);
 
     return OK;
@@ -227,29 +230,33 @@
         return err;
     }
 
-    jclass byteBufferClass = env->FindClass("java/nio/ByteBuffer");
-    CHECK(byteBufferClass != NULL);
+    ScopedLocalRef<jclass> byteBufferClass(
+            env, env->FindClass("java/nio/ByteBuffer"));
+
+    CHECK(byteBufferClass.get() != NULL);
 
     jmethodID orderID = env->GetMethodID(
-            byteBufferClass,
+            byteBufferClass.get(),
             "order",
             "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
 
     CHECK(orderID != NULL);
 
-    jclass byteOrderClass = env->FindClass("java/nio/ByteOrder");
-    CHECK(byteOrderClass != NULL);
+    ScopedLocalRef<jclass> byteOrderClass(
+            env, env->FindClass("java/nio/ByteOrder"));
+
+    CHECK(byteOrderClass.get() != NULL);
 
     jmethodID nativeOrderID = env->GetStaticMethodID(
-            byteOrderClass, "nativeOrder", "()Ljava/nio/ByteOrder;");
+            byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;");
     CHECK(nativeOrderID != NULL);
 
     jobject nativeByteOrderObj =
-        env->CallStaticObjectMethod(byteOrderClass, nativeOrderID);
+        env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID);
     CHECK(nativeByteOrderObj != NULL);
 
     *bufArray = (jobjectArray)env->NewObjectArray(
-            buffers.size(), byteBufferClass, NULL);
+            buffers.size(), byteBufferClass.get(), NULL);
     if (*bufArray == NULL) {
         env->DeleteLocalRef(nativeByteOrderObj);
         return NO_MEMORY;
@@ -303,6 +310,10 @@
     return OK;
 }
 
+status_t JMediaCodec::setParameters(const sp<AMessage> &msg) {
+    return mCodec->setParameters(msg);
+}
+
 void JMediaCodec::setVideoScalingMode(int mode) {
     if (mSurfaceTextureClient != NULL) {
         native_window_set_scaling_mode(mSurfaceTextureClient.get(), mode);
@@ -338,11 +349,12 @@
 }
 
 static void throwCryptoException(JNIEnv *env, status_t err, const char *msg) {
-    jclass clazz = env->FindClass("android/media/MediaCodec$CryptoException");
-    CHECK(clazz != NULL);
+    ScopedLocalRef<jclass> clazz(
+            env, env->FindClass("android/media/MediaCodec$CryptoException"));
+    CHECK(clazz.get() != NULL);
 
     jmethodID constructID =
-        env->GetMethodID(clazz, "<init>", "(ILjava/lang/String;)V");
+        env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V");
     CHECK(constructID != NULL);
 
     jstring msgObj = env->NewStringUTF(msg != NULL ? msg : "Unknown Error");
@@ -363,7 +375,7 @@
     }
 
     jthrowable exception =
-        (jthrowable)env->NewObject(clazz, constructID, err, msgObj);
+        (jthrowable)env->NewObject(clazz.get(), constructID, err, msgObj);
 
     env->Throw(exception);
 }
@@ -829,6 +841,27 @@
     return NULL;
 }
 
+static void android_media_MediaCodec_setParameters(
+        JNIEnv *env, jobject thiz, jobjectArray keys, jobjectArray vals) {
+    ALOGV("android_media_MediaCodec_setParameters");
+
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+    if (codec == NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return;
+    }
+
+    sp<AMessage> params;
+    status_t err = ConvertKeyValueArraysToMessage(env, keys, vals, &params);
+
+    if (err == OK) {
+        err = codec->setParameters(params);
+    }
+
+    throwExceptionAsNecessary(env, err);
+}
+
 static void android_media_MediaCodec_setVideoScalingMode(
         JNIEnv *env, jobject thiz, jint mode) {
     sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -848,51 +881,55 @@
 }
 
 static void android_media_MediaCodec_native_init(JNIEnv *env) {
-    jclass clazz = env->FindClass("android/media/MediaCodec");
-    CHECK(clazz != NULL);
+    ScopedLocalRef<jclass> clazz(
+            env, env->FindClass("android/media/MediaCodec"));
+    CHECK(clazz.get() != NULL);
 
-    gFields.context = env->GetFieldID(clazz, "mNativeContext", "I");
+    gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "I");
     CHECK(gFields.context != NULL);
 
-    clazz = env->FindClass("android/media/MediaCodec$CryptoInfo");
-    CHECK(clazz != NULL);
+    clazz.reset(env->FindClass("android/media/MediaCodec$CryptoInfo"));
+    CHECK(clazz.get() != NULL);
 
     gFields.cryptoInfoNumSubSamplesID =
-        env->GetFieldID(clazz, "numSubSamples", "I");
+        env->GetFieldID(clazz.get(), "numSubSamples", "I");
     CHECK(gFields.cryptoInfoNumSubSamplesID != NULL);
 
     gFields.cryptoInfoNumBytesOfClearDataID =
-        env->GetFieldID(clazz, "numBytesOfClearData", "[I");
+        env->GetFieldID(clazz.get(), "numBytesOfClearData", "[I");
     CHECK(gFields.cryptoInfoNumBytesOfClearDataID != NULL);
 
     gFields.cryptoInfoNumBytesOfEncryptedDataID =
-        env->GetFieldID(clazz, "numBytesOfEncryptedData", "[I");
+        env->GetFieldID(clazz.get(), "numBytesOfEncryptedData", "[I");
     CHECK(gFields.cryptoInfoNumBytesOfEncryptedDataID != NULL);
 
-    gFields.cryptoInfoKeyID = env->GetFieldID(clazz, "key", "[B");
+    gFields.cryptoInfoKeyID = env->GetFieldID(clazz.get(), "key", "[B");
     CHECK(gFields.cryptoInfoKeyID != NULL);
 
-    gFields.cryptoInfoIVID = env->GetFieldID(clazz, "iv", "[B");
+    gFields.cryptoInfoIVID = env->GetFieldID(clazz.get(), "iv", "[B");
     CHECK(gFields.cryptoInfoIVID != NULL);
 
-    gFields.cryptoInfoModeID = env->GetFieldID(clazz, "mode", "I");
+    gFields.cryptoInfoModeID = env->GetFieldID(clazz.get(), "mode", "I");
     CHECK(gFields.cryptoInfoModeID != NULL);
 
-    clazz = env->FindClass("android/media/MediaCodec$CryptoException");
-    CHECK(clazz != NULL);
+    clazz.reset(env->FindClass("android/media/MediaCodec$CryptoException"));
+    CHECK(clazz.get() != NULL);
 
     jfieldID field;
-    field = env->GetStaticFieldID(clazz, "ERROR_NO_KEY", "I");
+    field = env->GetStaticFieldID(clazz.get(), "ERROR_NO_KEY", "I");
     CHECK(field != NULL);
-    gCryptoErrorCodes.cryptoErrorNoKey = env->GetStaticIntField(clazz, field);
+    gCryptoErrorCodes.cryptoErrorNoKey =
+        env->GetStaticIntField(clazz.get(), field);
 
-    field = env->GetStaticFieldID(clazz, "ERROR_KEY_EXPIRED", "I");
+    field = env->GetStaticFieldID(clazz.get(), "ERROR_KEY_EXPIRED", "I");
     CHECK(field != NULL);
-    gCryptoErrorCodes.cryptoErrorKeyExpired = env->GetStaticIntField(clazz, field);
+    gCryptoErrorCodes.cryptoErrorKeyExpired =
+        env->GetStaticIntField(clazz.get(), field);
 
-    field = env->GetStaticFieldID(clazz, "ERROR_RESOURCE_BUSY", "I");
+    field = env->GetStaticFieldID(clazz.get(), "ERROR_RESOURCE_BUSY", "I");
     CHECK(field != NULL);
-    gCryptoErrorCodes.cryptoErrorResourceBusy = env->GetStaticIntField(clazz, field);
+    gCryptoErrorCodes.cryptoErrorResourceBusy =
+        env->GetStaticIntField(clazz.get(), field);
 }
 
 static void android_media_MediaCodec_native_setup(
@@ -974,6 +1011,9 @@
     { "getName", "()Ljava/lang/String;",
       (void *)android_media_MediaCodec_getName },
 
+    { "setParameters", "([Ljava/lang/String;[Ljava/lang/Object;)V",
+      (void *)android_media_MediaCodec_setParameters },
+
     { "setVideoScalingMode", "(I)V",
       (void *)android_media_MediaCodec_setVideoScalingMode },
 
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index 282d2c5..2fbbd72 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -87,6 +87,8 @@
 
     status_t getName(JNIEnv *env, jstring *name) const;
 
+    status_t setParameters(const sp<AMessage> &params);
+
     void setVideoScalingMode(int mode);
 
 protected:
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index 04430ec..caa594e 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -110,10 +110,11 @@
 
     Vector<MediaCodecList::ProfileLevel> profileLevels;
     Vector<uint32_t> colorFormats;
+    uint32_t flags;
 
     status_t err =
         MediaCodecList::getInstance()->getCodecCapabilities(
-                index, typeStr, &profileLevels, &colorFormats);
+                index, typeStr, &profileLevels, &colorFormats, &flags);
 
     env->ReleaseStringUTFChars(type, typeStr);
     typeStr = NULL;
@@ -127,6 +128,9 @@
         env->FindClass("android/media/MediaCodecInfo$CodecCapabilities");
     CHECK(capsClazz != NULL);
 
+    jfieldID flagsField =
+        env->GetFieldID(capsClazz, "flags", "I");
+
     jobject caps = env->AllocObject(capsClazz);
 
     jclass profileLevelClazz =
@@ -163,6 +167,8 @@
 
     env->SetObjectField(caps, profileLevelsField, profileLevelArray);
 
+    env->SetIntField(caps, flagsField, flags);
+
     env->DeleteLocalRef(profileLevelArray);
     profileLevelArray = NULL;
 
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index e35ace3..54c5e9b 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -24,6 +24,8 @@
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/AMessage.h>
 
+#include <nativehelper/ScopedLocalRef.h>
+
 namespace android {
 
 bool ConvertKeyValueArraysToKeyedVector(
@@ -76,33 +78,35 @@
 }
 
 static jobject makeIntegerObject(JNIEnv *env, int32_t value) {
-    jclass clazz = env->FindClass("java/lang/Integer");
-    CHECK(clazz != NULL);
+    ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Integer"));
+    CHECK(clazz.get() != NULL);
 
-    jmethodID integerConstructID = env->GetMethodID(clazz, "<init>", "(I)V");
+    jmethodID integerConstructID =
+        env->GetMethodID(clazz.get(), "<init>", "(I)V");
     CHECK(integerConstructID != NULL);
 
-    return env->NewObject(clazz, integerConstructID, value);
+    return env->NewObject(clazz.get(), integerConstructID, value);
 }
 
 static jobject makeLongObject(JNIEnv *env, int64_t value) {
-    jclass clazz = env->FindClass("java/lang/Long");
-    CHECK(clazz != NULL);
+    ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Long"));
+    CHECK(clazz.get() != NULL);
 
-    jmethodID longConstructID = env->GetMethodID(clazz, "<init>", "(J)V");
+    jmethodID longConstructID = env->GetMethodID(clazz.get(), "<init>", "(J)V");
     CHECK(longConstructID != NULL);
 
-    return env->NewObject(clazz, longConstructID, value);
+    return env->NewObject(clazz.get(), longConstructID, value);
 }
 
 static jobject makeFloatObject(JNIEnv *env, float value) {
-    jclass clazz = env->FindClass("java/lang/Float");
-    CHECK(clazz != NULL);
+    ScopedLocalRef<jclass> clazz(env, env->FindClass("java/lang/Float"));
+    CHECK(clazz.get() != NULL);
 
-    jmethodID floatConstructID = env->GetMethodID(clazz, "<init>", "(F)V");
+    jmethodID floatConstructID =
+        env->GetMethodID(clazz.get(), "<init>", "(F)V");
     CHECK(floatConstructID != NULL);
 
-    return env->NewObject(clazz, floatConstructID, value);
+    return env->NewObject(clazz.get(), floatConstructID, value);
 }
 
 static jobject makeByteBufferObject(
@@ -110,15 +114,16 @@
     jbyteArray byteArrayObj = env->NewByteArray(size);
     env->SetByteArrayRegion(byteArrayObj, 0, size, (const jbyte *)data);
 
-    jclass clazz = env->FindClass("java/nio/ByteBuffer");
-    CHECK(clazz != NULL);
+    ScopedLocalRef<jclass> clazz(env, env->FindClass("java/nio/ByteBuffer"));
+    CHECK(clazz.get() != NULL);
 
     jmethodID byteBufWrapID =
-        env->GetStaticMethodID(clazz, "wrap", "([B)Ljava/nio/ByteBuffer;");
+        env->GetStaticMethodID(
+                clazz.get(), "wrap", "([B)Ljava/nio/ByteBuffer;");
     CHECK(byteBufWrapID != NULL);
 
     jobject byteBufObj = env->CallStaticObjectMethod(
-            clazz, byteBufWrapID, byteArrayObj);
+            clazz.get(), byteBufWrapID, byteArrayObj);
 
     env->DeleteLocalRef(byteArrayObj); byteArrayObj = NULL;
 
@@ -140,14 +145,15 @@
 
 status_t ConvertMessageToMap(
         JNIEnv *env, const sp<AMessage> &msg, jobject *map) {
-    jclass hashMapClazz = env->FindClass("java/util/HashMap");
+    ScopedLocalRef<jclass> hashMapClazz(
+            env, env->FindClass("java/util/HashMap"));
 
-    if (hashMapClazz == NULL) {
+    if (hashMapClazz.get() == NULL) {
         return -EINVAL;
     }
 
     jmethodID hashMapConstructID =
-        env->GetMethodID(hashMapClazz, "<init>", "()V");
+        env->GetMethodID(hashMapClazz.get(), "<init>", "()V");
 
     if (hashMapConstructID == NULL) {
         return -EINVAL;
@@ -155,7 +161,7 @@
 
     jmethodID hashMapPutID =
         env->GetMethodID(
-                hashMapClazz,
+                hashMapClazz.get(),
                 "put",
                 "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
 
@@ -163,7 +169,7 @@
         return -EINVAL;
     }
 
-    jobject hashMap = env->NewObject(hashMapClazz, hashMapConstructID);
+    jobject hashMap = env->NewObject(hashMapClazz.get(), hashMapConstructID);
 
     for (size_t i = 0; i < msg->countEntries(); ++i) {
         AMessage::Type valueType;
@@ -276,17 +282,16 @@
 status_t ConvertKeyValueArraysToMessage(
         JNIEnv *env, jobjectArray keys, jobjectArray values,
         sp<AMessage> *out) {
-    jclass stringClass = env->FindClass("java/lang/String");
-    CHECK(stringClass != NULL);
-
-    jclass integerClass = env->FindClass("java/lang/Integer");
-    CHECK(integerClass != NULL);
-
-    jclass floatClass = env->FindClass("java/lang/Float");
-    CHECK(floatClass != NULL);
-
-    jclass byteBufClass = env->FindClass("java/nio/ByteBuffer");
-    CHECK(byteBufClass != NULL);
+    ScopedLocalRef<jclass> stringClass(env, env->FindClass("java/lang/String"));
+    CHECK(stringClass.get() != NULL);
+    ScopedLocalRef<jclass> integerClass(env, env->FindClass("java/lang/Integer"));
+    CHECK(integerClass.get() != NULL);
+    ScopedLocalRef<jclass> longClass(env, env->FindClass("java/lang/Long"));
+    CHECK(longClass.get() != NULL);
+    ScopedLocalRef<jclass> floatClass(env, env->FindClass("java/lang/Float"));
+    CHECK(floatClass.get() != NULL);
+    ScopedLocalRef<jclass> byteBufClass(env, env->FindClass("java/nio/ByteBuffer"));
+    CHECK(byteBufClass.get() != NULL);
 
     sp<AMessage> msg = new AMessage;
 
@@ -309,7 +314,7 @@
     for (jsize i = 0; i < numEntries; ++i) {
         jobject keyObj = env->GetObjectArrayElement(keys, i);
 
-        if (!env->IsInstanceOf(keyObj, stringClass)) {
+        if (!env->IsInstanceOf(keyObj, stringClass.get())) {
             return -EINVAL;
         }
 
@@ -326,7 +331,7 @@
 
         jobject valueObj = env->GetObjectArrayElement(values, i);
 
-        if (env->IsInstanceOf(valueObj, stringClass)) {
+        if (env->IsInstanceOf(valueObj, stringClass.get())) {
             const char *value = env->GetStringUTFChars((jstring)valueObj, NULL);
 
             if (value == NULL) {
@@ -337,29 +342,37 @@
 
             env->ReleaseStringUTFChars((jstring)valueObj, value);
             value = NULL;
-        } else if (env->IsInstanceOf(valueObj, integerClass)) {
+        } else if (env->IsInstanceOf(valueObj, integerClass.get())) {
             jmethodID intValueID =
-                env->GetMethodID(integerClass, "intValue", "()I");
+                env->GetMethodID(integerClass.get(), "intValue", "()I");
             CHECK(intValueID != NULL);
 
             jint value = env->CallIntMethod(valueObj, intValueID);
 
             msg->setInt32(key.c_str(), value);
-        } else if (env->IsInstanceOf(valueObj, floatClass)) {
+        } else if (env->IsInstanceOf(valueObj, longClass.get())) {
+            jmethodID longValueID =
+                env->GetMethodID(longClass.get(), "longValue", "()J");
+            CHECK(longValueID != NULL);
+
+            jlong value = env->CallLongMethod(valueObj, longValueID);
+
+            msg->setInt64(key.c_str(), value);
+        } else if (env->IsInstanceOf(valueObj, floatClass.get())) {
             jmethodID floatValueID =
-                env->GetMethodID(floatClass, "floatValue", "()F");
+                env->GetMethodID(floatClass.get(), "floatValue", "()F");
             CHECK(floatValueID != NULL);
 
             jfloat value = env->CallFloatMethod(valueObj, floatValueID);
 
             msg->setFloat(key.c_str(), value);
-        } else if (env->IsInstanceOf(valueObj, byteBufClass)) {
+        } else if (env->IsInstanceOf(valueObj, byteBufClass.get())) {
             jmethodID positionID =
-                env->GetMethodID(byteBufClass, "position", "()I");
+                env->GetMethodID(byteBufClass.get(), "position", "()I");
             CHECK(positionID != NULL);
 
             jmethodID limitID =
-                env->GetMethodID(byteBufClass, "limit", "()I");
+                env->GetMethodID(byteBufClass.get(), "limit", "()I");
             CHECK(limitID != NULL);
 
             jint position = env->CallIntMethod(valueObj, positionID);
@@ -375,7 +388,7 @@
                        buffer->size());
             } else {
                 jmethodID arrayID =
-                    env->GetMethodID(byteBufClass, "array", "()[B");
+                    env->GetMethodID(byteBufClass.get(), "array", "()[B");
                 CHECK(arrayID != NULL);
 
                 jbyteArray byteArray =
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 74fd7a8..c00639d 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -50,6 +50,12 @@
             android:theme="@style/PrintJobConfigActivityTheme">
         </activity>
 
+        <activity
+            android:name=".ChoosePrinterActivity"
+            android:exported="false"
+            android:theme="@android:style/Theme.Holo.Light">
+        </activity>
+
         <receiver
             android:name=".NotificationController$NotificationBroadcastReceiver"
             android:exported="false" >
diff --git a/packages/PrintSpooler/res/layout/choose_printer_activity.xml b/packages/PrintSpooler/res/layout/choose_printer_activity.xml
new file mode 100644
index 0000000..c34a108
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/choose_printer_activity.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/list_view"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+</ListView>
+
diff --git a/packages/PrintSpooler/res/menu/choose_printer_activity.xml b/packages/PrintSpooler/res/menu/choose_printer_activity.xml
new file mode 100644
index 0000000..3774279
--- /dev/null
+++ b/packages/PrintSpooler/res/menu/choose_printer_activity.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/action_search"
+        android:title="@string/search"
+        android:icon="@*android:drawable/ic_menu_search_holo_light"
+        android:actionViewClass="android.widget.SearchView"
+        android:showAsAction="ifRoom"
+        android:alphabeticShortcut="f"
+         android:imeOptions="actionSearch">
+    </item>
+
+</menu>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 2086f58..1cd611f 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -58,6 +58,11 @@
     <!-- Title for the temporary dialog show while an app is generating a print job. [CHAR LIMIT=30] -->
     <string name="generating_print_job">Generating print job</string>
 
+    <!-- Choose printer activity -->
+
+    <!-- Title for the share action bar menu item. [CHAR LIMIT=20] -->
+    <string name="search">Search</string>
+
     <!-- Notifications -->
 
     <!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java b/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java
new file mode 100644
index 0000000..658a224
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.print.IPrinterDiscoverySessionController;
+import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class is responsible to provide the available printers.
+ * It starts and stops printer discovery and manages the returned
+ * printers.
+ */
+public class AvailablePrinterProvider extends DataProvider<PrinterInfo>
+        implements DataLoader {
+    private static final String LOG_TAG = "AvailablePrinterProvider";
+
+    private final Set<PrinterId> mPrinteIdsSet = new ArraySet<PrinterId>();
+
+    private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
+
+    private final List<PrinterId> mPriorityList;
+
+    private PrinterDiscoverySession mDiscoverySession;
+
+    public AvailablePrinterProvider(Context context, List<PrinterId> priorityList) {
+        mDiscoverySession = new PrinterDiscoverySession(context.getMainLooper());
+        mPriorityList = priorityList;
+    }
+
+    @Override
+    public void startLoadData() {
+        mDiscoverySession.open();
+    }
+
+    @Override
+    public void stopLoadData() {
+        mDiscoverySession.close();
+    }
+
+    @Override
+    public int getItemCount() {
+        return mPrinters.size();
+    }
+
+    @Override
+    public int getItemIndex(PrinterInfo printer) {
+        return mPrinters.indexOf(printer);
+    }
+
+    @Override
+    public PrinterInfo getItemAt(int index) {
+        return mPrinters.get(index);
+    }
+
+    public void refreshItem(int index) {
+        PrinterInfo printer = getItemAt(index);
+        mDiscoverySession.requestPrinterUpdate(printer.getId());
+    }
+
+    private void addPrinters(List<PrinterInfo> printers) {
+        boolean addedPrinters = false;
+
+        final int addedPrinterCount = printers.size();
+        for (int i = 0; i < addedPrinterCount; i++) {
+           PrinterInfo addedPrinter = printers.get(i);
+           if (mPrinteIdsSet.add(addedPrinter.getId())) {
+               mPrinters.add(addedPrinter);
+               addedPrinters = true;
+           }
+        }
+
+        if (addedPrinters) {
+            notifyChanged();
+        }
+    }
+
+    private void updatePrinters(List<PrinterInfo> printers) {
+        boolean updatedPrinters = false;
+
+        final int updatedPrinterCount = printers.size();
+        for (int i = 0; i < updatedPrinterCount; i++) {
+            PrinterInfo updatedPrinter = printers.get(i);
+            if (mPrinteIdsSet.contains(updatedPrinter.getId())) {
+                final int oldPrinterCount = mPrinters.size();
+                for (int j = 0; j < oldPrinterCount; j++) {
+                    PrinterInfo oldPrinter = mPrinters.get(j);
+                    if (updatedPrinter.getId().equals(oldPrinter.getId())) {
+                        mPrinters.set(j, updatedPrinter);
+                        updatedPrinters = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (updatedPrinters) {
+            notifyChanged();
+        }
+    }
+
+    private void removePrinters(List<PrinterId> printers) {
+        boolean removedPrinters = false;
+
+        final int removedPrinterCount = printers.size();
+        for (int i = 0; i < removedPrinterCount; i++) {
+            PrinterId removedPrinter = printers.get(i);
+            if (mPrinteIdsSet.contains(removedPrinter)) {
+                mPrinteIdsSet.remove(removedPrinter);
+                Iterator<PrinterInfo> iterator = mPrinters.iterator();
+                while (iterator.hasNext()) {
+                    PrinterInfo oldPrinter = iterator.next();
+                    if (removedPrinter.equals(oldPrinter.getId())) {
+                        iterator.remove();
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (removedPrinters) {
+            notifyChanged();
+        }
+    }
+
+    private final class PrinterDiscoverySession {
+
+        private final Handler mHandler;
+
+        private final IPrinterDiscoverySessionObserver mObserver;
+
+        private IPrinterDiscoverySessionController mController;
+
+        public PrinterDiscoverySession(Looper looper) {
+            mHandler = new SessionHandler(looper);
+            mObserver = new PrinterDiscoverySessionObserver(this);
+        }
+
+        public void open() {
+            PrintSpooler.peekInstance().createPrinterDiscoverySession(
+                    mObserver);
+        }
+
+        public void close() {
+            if (mController != null) {
+                try {
+                    mController.close();
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error closing printer discovery session", re);
+                } finally {
+                    mController = null;
+                }
+            }
+        }
+
+        public void requestPrinterUpdate(PrinterId printerId) {
+            if (mController != null) {
+                try {
+                    mController.requestPrinterUpdate(printerId);
+                } catch (RemoteException re) {
+                    Log.e(LOG_TAG, "Error requesting printer udpdate", re);
+                }
+            }
+        }
+
+        private final class SessionHandler extends Handler {
+            public static final int MSG_SET_CONTROLLER = 1;
+            public static final int MSG_ON_PRINTERS_ADDED = 2;
+            public static final int MSG_ON_PRINTERS_REMOVED = 3;
+            public static final int MSG_ON_PRINTERS_UPDATED = 4;
+
+            public SessionHandler(Looper looper) {
+                super(looper, null, false);
+            }
+
+            @Override
+            @SuppressWarnings("unchecked")
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_SET_CONTROLLER: {
+                        mController = (IPrinterDiscoverySessionController) message.obj;
+                        try {
+                            mController.open(mPriorityList);
+                        } catch (RemoteException e) {
+                            Log.e(LOG_TAG, "Error starting printer discovery");
+                        }
+                    } break;
+
+                    case MSG_ON_PRINTERS_ADDED: {
+                        List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+                        addPrinters(printers);
+                    } break;
+
+                    case MSG_ON_PRINTERS_REMOVED: {
+                        List<PrinterId> printers = (List<PrinterId>) message.obj;
+                        removePrinters(printers);
+                    } break;
+
+                    case MSG_ON_PRINTERS_UPDATED: {
+                        List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
+                        updatePrinters(printers);
+                    } break;
+                };
+            }
+        }
+    }
+
+    private static final class PrinterDiscoverySessionObserver
+            extends IPrinterDiscoverySessionObserver.Stub {
+
+        private final WeakReference<PrinterDiscoverySession> mWeakSession;
+
+        public PrinterDiscoverySessionObserver(PrinterDiscoverySession session) {
+            mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
+        }
+
+        @Override
+        public void setController(IPrinterDiscoverySessionController controller) {
+            PrinterDiscoverySession sesison = mWeakSession.get();
+            if (sesison != null) {
+                sesison.mHandler.obtainMessage(
+                        PrinterDiscoverySession.SessionHandler.MSG_SET_CONTROLLER,
+                        controller).sendToTarget();
+            }
+        }
+
+        @Override
+        public void onPrintersAdded(List<PrinterInfo> printers) {
+            PrinterDiscoverySession sesison = mWeakSession.get();
+            if (sesison != null) {
+                sesison.mHandler.obtainMessage(
+                        PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_ADDED,
+                        printers).sendToTarget();
+            }
+        }
+
+        @Override
+        public void onPrintersRemoved(List<PrinterId> printers) {
+            PrinterDiscoverySession session = mWeakSession.get();
+            if (session != null) {
+                session.mHandler.obtainMessage(
+                        PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_REMOVED,
+                        printers).sendToTarget();
+            }
+        }
+
+        @Override
+        public void onPrintersUpdated(List<PrinterInfo> printers) {
+            PrinterDiscoverySession session = mWeakSession.get();
+            if (session != null) {
+                session.mHandler.obtainMessage(
+                        PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_UPDATED,
+                        printers).sendToTarget();
+            }
+        }
+    };
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java
new file mode 100644
index 0000000..8b0dd66a
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class ChoosePrinterActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        setContentView(R.layout.choose_printer_activity);
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java b/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java
new file mode 100644
index 0000000..82cc2e1
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler;
+
+/**
+ * This is the contract for a class that know how to load data.
+ */
+public interface DataLoader {
+
+    /**
+     * Requests to start loading data.
+     */
+    public void startLoadData();
+
+    /**
+     * Requests to stop loading data.
+     */
+    public void stopLoadData();
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java b/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java
new file mode 100644
index 0000000..7b10903
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler;
+
+import android.database.DataSetObservable;
+
+/**
+ * This is the simple contract for data providers.
+ *
+ * @param <T> The type of the providers data.
+ */
+public abstract class DataProvider<T> extends DataSetObservable {
+
+    /**
+     * Gets the number of items.
+     *
+     * @return The item count.
+     */
+    public abstract int getItemCount();
+
+    /**
+     * Gets the index of an item.
+     *
+     * @param item The item.
+     * @return The item index.
+     */
+    public abstract int getItemIndex(T item);
+
+    /**
+     * Gets an item at a given position.
+     *
+     * @param index The position.
+     * @return The item.
+     */
+    public abstract T getItemAt(int index);
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java
new file mode 100644
index 0000000..2c539d1
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.printspooler;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class provides the favorite printers based on past usage.
+ */
+final class FavoritePrinterProvider extends DataProvider<PrinterInfo> implements DataLoader {
+
+    private static final String LOG_TAG = "FavoritePrinterProvider";
+
+    private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
+
+    private static final int MAX_HISTORY_LENGTH = 50;
+
+    private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
+
+    private final List<PrinterRecord> mHistoricalPrinters = new ArrayList<PrinterRecord>();
+
+    private final List<PrinterRecord> mFavoritePrinters = new ArrayList<PrinterRecord>();
+
+    private final PersistenceManager mPersistenceManager;
+
+    public FavoritePrinterProvider(Context context) {
+        mPersistenceManager = new PersistenceManager(context);
+    }
+
+    public void addPrinter(PrinterInfo printer) {
+        addPrinterInternal(printer);
+        computeFavoritePrinters();
+        mPersistenceManager.writeState();
+    }
+
+    @Override
+    public int getItemCount() {
+        return mFavoritePrinters.size();
+    }
+
+    @Override
+    public PrinterInfo getItemAt(int index) {
+        return mFavoritePrinters.get(index).printer;
+    }
+
+    @Override
+    public int getItemIndex(PrinterInfo printer) {
+        return mFavoritePrinters.indexOf(printer);
+    }
+
+    @Override
+    public void startLoadData() {
+        mPersistenceManager.readStateLocked();
+        computeFavoritePrinters();
+    }
+
+    @Override
+    public void stopLoadData() {
+        /* do nothing */
+    }
+
+    private void addPrinterInternal(PrinterInfo printer) {
+        if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
+            mHistoricalPrinters.remove(0);
+        }
+        mHistoricalPrinters.add(new PrinterRecord(printer));
+    }
+
+    private void computeFavoritePrinters() {
+        Map<PrinterId, PrinterRecord> recordMap =
+                new ArrayMap<PrinterId, PrinterRecord>();
+
+        // Recompute the weights.
+        float currentWeight = 1.0f;
+        final int printerCount = mHistoricalPrinters.size();
+        for (int i = printerCount - 1; i >= 0; i--) {
+            PrinterRecord record = mHistoricalPrinters.get(i);
+            record.weight = currentWeight;
+            // Aggregate weight for the same printer
+            PrinterRecord oldRecord = recordMap.put(record.printer.getId(), record);
+            if (oldRecord != null) {
+                record.weight += oldRecord.weight;
+            }
+            currentWeight *= WEIGHT_DECAY_COEFFICIENT;
+        }
+
+        // Copy the unique printer records with computed weights.
+        mFavoritePrinters.addAll(recordMap.values());
+
+        // Soft the favorite printers.
+        Collections.sort(mFavoritePrinters);
+    }
+
+    private final class PrinterRecord implements Comparable<PrinterRecord> {
+        public final PrinterInfo printer;
+        public float weight;
+
+        public PrinterRecord(PrinterInfo printer) {
+            this.printer = printer;
+        }
+
+        @Override
+        public int compareTo(PrinterRecord another) {
+            return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
+        }
+    }
+
+    private final class PersistenceManager {
+        private static final String PERSIST_FILE_NAME = "printer_history.xml";
+
+        private static final String TAG_PRINTERS = "printers";
+
+        private static final String TAG_PRINTER = "printer";
+        private static final String TAG_PRINTER_ID = "printerId";
+
+        private static final String ATTR_LOCAL_ID = "localId";
+        private static final String ATTR_SERVICE_NAME = "serviceName";
+
+        private static final String ATTR_NAME = "name";
+        private static final String ATTR_DESCRIPTION = "description";
+        private static final String ATTR_STATUS = "status";
+
+        private final AtomicFile mStatePersistFile;
+
+        private PersistenceManager(Context context) {
+            mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
+                    PERSIST_FILE_NAME));
+        }
+
+        @SuppressWarnings("unchecked")
+        public void writeState() {
+
+            new AsyncTask<List<PrinterRecord>, Void, Void>() {
+                @Override
+                protected Void doInBackground(List<PrinterRecord>... printers) {
+                    doWriteState(printers[0]);
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Void result) {
+                    notifyChanged();
+                }
+
+            }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
+                    new ArrayList<PrinterRecord>(mHistoricalPrinters));
+        }
+
+        private void doWriteState(List<PrinterRecord> printers) {
+            FileOutputStream out = null;
+            try {
+                out = mStatePersistFile.startWrite();
+
+                XmlSerializer serializer = new FastXmlSerializer();
+                serializer.setOutput(out, "utf-8");
+                serializer.startDocument(null, true);
+                serializer.startTag(null, TAG_PRINTERS);
+
+                final int printerCount = printers.size();
+                for (int i = printerCount - 1; i >= 0; i--) {
+                    PrinterInfo printer = printers.get(i).printer;
+
+                    serializer.startTag(null, TAG_PRINTER);
+
+                    serializer.attribute(null, ATTR_NAME, printer.getName());
+                    serializer.attribute(null, ATTR_STATUS, String.valueOf(printer.getStatus()));
+                    String description = printer.getDescription();
+                    if (description != null) {
+                        serializer.attribute(null, ATTR_DESCRIPTION, description);
+                    }
+
+                    PrinterId printerId = printer.getId();
+                    serializer.startTag(null, TAG_PRINTER_ID);
+                    serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+                    serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+                            .flattenToString());
+                    serializer.endTag(null, TAG_PRINTER_ID);
+
+                    serializer.endTag(null, TAG_PRINTER);
+
+                    if (DEBUG) {
+                        Log.i(LOG_TAG, "[PERSISTED] " + printer);
+                    }
+                }
+
+                serializer.endTag(null, TAG_PRINTERS);
+                serializer.endDocument();
+                mStatePersistFile.finishWrite(out);
+
+                if (DEBUG) {
+                    Log.i(LOG_TAG, "[PERSIST END]");
+                }
+            } catch (IOException ioe) {
+                Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
+                mStatePersistFile.failWrite(out);
+            } finally {
+                IoUtils.closeQuietly(out);
+            }
+        }
+
+        public void readStateLocked() {
+            FileInputStream in = null;
+            try {
+                in = mStatePersistFile.openRead();
+            } catch (FileNotFoundException e) {
+                Log.i(LOG_TAG, "No existing printer history.");
+                return;
+            }
+            try {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(in, null);
+                parseState(parser);
+            } catch (IllegalStateException ise) {
+                Slog.w(LOG_TAG, "Failed parsing ", ise);
+            } catch (NullPointerException npe) {
+                Slog.w(LOG_TAG, "Failed parsing ", npe);
+            } catch (NumberFormatException nfe) {
+                Slog.w(LOG_TAG, "Failed parsing ", nfe);
+            } catch (XmlPullParserException xppe) {
+                Slog.w(LOG_TAG, "Failed parsing ", xppe);
+            } catch (IOException ioe) {
+                Slog.w(LOG_TAG, "Failed parsing ", ioe);
+            } catch (IndexOutOfBoundsException iobe) {
+                Slog.w(LOG_TAG, "Failed parsing ", iobe);
+            } finally {
+                IoUtils.closeQuietly(in);
+            }
+            notifyChanged();
+        }
+
+        private void parseState(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            parser.next();
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
+            parser.next();
+
+            while (parsePrinter(parser)) {
+                parser.next();
+            }
+
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
+
+            // We were reading the new records first and appended them first,
+            // hence the historical list is in a reversed order, so fix that.
+            Collections.reverse(mHistoricalPrinters);
+        }
+
+        private boolean parsePrinter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            skipEmptyTextTags(parser);
+            if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
+                return false;
+            }
+
+            String name = parser.getAttributeValue(null, ATTR_NAME);
+            String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
+            final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
+
+            parser.next();
+
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
+            String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+            ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+                    null, ATTR_SERVICE_NAME));
+            PrinterId printerId =  new PrinterId(service, localId);
+            parser.next();
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+            parser.next();
+
+            PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
+            builder.setDescription(description);
+            PrinterInfo printer = builder.create();
+
+            addPrinterInternal(printer);
+
+            if (DEBUG) {
+                Log.i(LOG_TAG, "[RESTORED] " + printer);
+            }
+
+            skipEmptyTextTags(parser);
+            expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
+
+            return true;
+        }
+
+        private void expect(XmlPullParser parser, int type, String tag)
+                throws IOException, XmlPullParserException {
+            if (!accept(parser, type, tag)) {
+                throw new XmlPullParserException("Exepected event: " + type
+                        + " and tag: " + tag + " but got event: " + parser.getEventType()
+                        + " and tag:" + parser.getName());
+            }
+        }
+
+        private void skipEmptyTextTags(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            while (accept(parser, XmlPullParser.TEXT, null)
+                    && "\n".equals(parser.getText())) {
+                parser.next();
+            }
+        }
+
+        private boolean accept(XmlPullParser parser, int type, String tag)
+                throws IOException, XmlPullParserException {
+            if (parser.getEventType() != type) {
+                return false;
+            }
+            if (tag != null) {
+                if (!tag.equals(parser.getName())) {
+                    return false;
+                }
+            } else if (parser.getName() != null) {
+                return false;
+            }
+            return true;
+        }
+    }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 9160b7d..f8e9f43 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -22,6 +22,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.DataSetObserver;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -34,8 +35,6 @@
 import android.os.RemoteException;
 import android.print.ILayoutResultCallback;
 import android.print.IPrintDocumentAdapter;
-import android.print.IPrinterDiscoverySessionController;
-import android.print.IPrinterDiscoverySessionObserver;
 import android.print.IWriteResultCallback;
 import android.print.PageRange;
 import android.print.PrintAttributes;
@@ -64,6 +63,7 @@
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.Spinner;
@@ -72,7 +72,6 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -132,11 +131,13 @@
         }
     };
 
-    private PrintSpooler mSpooler;
     private Editor mEditor;
     private Document mDocument;
     private PrintController mController;
-    private PrinterDiscoverySessionObserver mPrinterDiscoverySessionObserver;
+
+    private AvailablePrinterProvider mAvailablePrinters;
+
+    private FavoritePrinterProvider mFavoritePrinters;
 
     private int mPrintJobId;
 
@@ -168,12 +169,15 @@
             mCurrPrintAttributes.copyFrom(attributes);
         }
 
-        mSpooler = PrintSpooler.peekInstance();
+        // TODO: Use history
+        mAvailablePrinters = new AvailablePrinterProvider(this, null);
+        mFavoritePrinters = new FavoritePrinterProvider(this);
+
         mEditor = new Editor();
         mDocument = new Document();
         mController = new PrintController(new RemotePrintDocumentAdapter(
                 IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
-                mSpooler.generateFileForPrintJob(mPrintJobId)));
+                PrintSpooler.peekInstance().generateFileForPrintJob(mPrintJobId)));
 
         try {
             mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
@@ -184,9 +188,26 @@
 
         mController.initialize();
         mEditor.initialize();
-        mPrinterDiscoverySessionObserver = new PrinterDiscoverySessionObserver(mEditor,
-                getMainLooper());
-        mSpooler.createPrinterDiscoverySession(mPrinterDiscoverySessionObserver);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // TODO: Polish this
+        if (!mEditor.isPrintConfirmed()) {
+            mAvailablePrinters.startLoadData();
+            mFavoritePrinters.startLoadData();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        // TODO: Polish this
+        if (!mEditor.isPrintConfirmed()) {
+            mAvailablePrinters.stopLoadData();
+            mFavoritePrinters.stopLoadData();
+        }
+        super.onPause();
     }
 
     @Override
@@ -194,17 +215,14 @@
         // We can safely do the work in here since at this point
         // the system is bound to our (spooler) process which
         // guarantees that this process will not be killed.
-        mPrinterDiscoverySessionObserver.close();
-        mPrinterDiscoverySessionObserver.destroy();
-        mPrinterDiscoverySessionObserver = null;
         if (mController.hasStarted()) {
             mController.finish();
         }
         if (mEditor.isPrintConfirmed() && mController.isFinished()) {
-            mSpooler.setPrintJobState(mPrintJobId,
+            PrintSpooler.peekInstance().setPrintJobState(mPrintJobId,
                     PrintJobInfo.STATE_QUEUED, null);
         } else {
-            mSpooler.setPrintJobState(mPrintJobId,
+            PrintSpooler.peekInstance().setPrintJobState(mPrintJobId,
                     PrintJobInfo.STATE_CANCELED, null);
         }
         mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
@@ -320,7 +338,8 @@
                 // completed and nothing changed, so we handle writing as usual.
                 handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get());
             } else {
-                mSpooler.setPrintJobAttributesNoPersistence(mPrintJobId, mCurrPrintAttributes);
+                PrintSpooler.peekInstance().setPrintJobAttributesNoPersistence(mPrintJobId,
+                        mCurrPrintAttributes);
 
                 mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW,
                         !mEditor.isPrintConfirmed());
@@ -353,15 +372,14 @@
             }
 
             mControllerState = CONTROLLER_STATE_LAYOUT_COMPLETED;
+            mEditor.updateUi();
 
-            // If the info changed, we update the document and the print job,
-            // and update the UI since the the page range selection may have
-            // become invalid.
+            // If the info changed, we update the document and the print job.
             final boolean infoChanged = !info.equals(mDocument.info);
             if (infoChanged) {
                 mDocument.info = info;
-                mSpooler.setPrintJobPrintDocumentInfoNoPersistence(mPrintJobId, info);
-                mEditor.updateUi();
+                PrintSpooler.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
+                        mPrintJobId, info);
             }
 
             // If the document info or the layout changed, then
@@ -447,11 +465,13 @@
             if (Arrays.equals(mDocument.pages, mRequestedPages)) {
                 // We got a document with exactly the pages we wanted. Hence,
                 // the printer has to print all pages in the data.
-                mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, ALL_PAGES_ARRAY);
+                PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                        ALL_PAGES_ARRAY);
             } else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) {
                 // We requested specific pages but got all of them. Hence,
                 // the printer has to print only the requested pages.
-                mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mRequestedPages);
+                PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                        mRequestedPages);
             } else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
                 // We requested specific pages and got more but not all pages.
                 // Hence, we have to offset appropriately the printed pages to
@@ -460,14 +480,16 @@
                 final int offset = mDocument.pages[0].getStart() - pages[0].getStart();
                 PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length);
                 PageRangeUtils.offsetStart(offsetPages, offset);
-                mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, offsetPages);
+                PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                        offsetPages);
             } else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY)
                     && mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0
                     && mDocument.pages[0].getEnd() == mDocument.info.getPageCount() - 1) {
                 // We requested all pages via the special constant and got all
                 // of them as an explicit enumeration. Hence, the printer has
                 // to print only the requested pages.
-                mSpooler.setPrintJobPagesNoPersistence(mPrintJobId, mDocument.pages);
+                PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+                        mDocument.pages);
             } else {
                 // We did not get the pages we requested, then the application
                 // misbehaves, so we fail quickly.
@@ -592,7 +614,7 @@
         private final EditText mRangeEditText;
 
         private final Spinner mDestinationSpinner;
-        private final ArrayAdapter<SpinnerItem<PrinterInfo>> mDestinationSpinnerAdapter;
+        private final DestinationAdapter mDestinationSpinnerAdapter;
 
         private final Spinner mMediaSizeSpinner;
         private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
@@ -623,15 +645,18 @@
                         return;
                     }
                     mCurrPrintAttributes.clear();
-                    SpinnerItem<PrinterInfo> dstItem = mDestinationSpinnerAdapter.getItem(position);
-                    if (dstItem != null) {
-                        PrinterInfo printer = dstItem.value;
-                        mSpooler.setPrintJobPrinterNoPersistence(mPrintJobId, printer);
+                    PrinterInfo printer = (PrinterInfo) mDestinationSpinnerAdapter
+                            .getItem(position);
+                    if (printer != null) {
+                        PrintSpooler.peekInstance().setPrintJobPrinterNoPersistence(
+                                mPrintJobId, printer);
                         PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
                         if (capabilities == null) {
                             List<PrinterId> printerIds = new ArrayList<PrinterId>();
                             printerIds.add(printer.getId());
-                            mPrinterDiscoverySessionObserver.requestPrinterUpdate(printer.getId());
+                            final int index = mAvailablePrinters.getItemIndex(printer);
+                            mAvailablePrinters.refreshItem(index);
+                            mWaitingForPrinterCapabilities = true;
                             //TODO: We need a timeout for the update.
                         } else {
                             capabilities.getDefaults(mCurrPrintAttributes);
@@ -728,7 +753,7 @@
                 }
 
                 mCopiesEditText.setError(null);
-                mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, copies);
+                PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, copies);
                 updateUi();
 
                 if (hadErrors && !hasErrors() && printAttributesChanged()) {
@@ -805,6 +830,8 @@
         private boolean mIgnoreNextCopiesChange;
         private boolean mIgnoreNextRangeChange;
 
+        private boolean mWaitingForPrinterCapabilities;
+
         public Editor() {
             // Content container
             mContentContainer = findViewById(R.id.content_container);
@@ -812,13 +839,40 @@
             // Copies
             mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
             mCopiesEditText.setText(String.valueOf(MIN_COPIES));
-            mSpooler.setPrintJobCopiesNoPersistence(mPrintJobId, MIN_COPIES);
+            PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, MIN_COPIES);
             mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
             mCopiesEditText.selectAll();
 
             // Destination.
+            mDestinationSpinnerAdapter = new DestinationAdapter(mAvailablePrinters);
+            mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() {
+                @Override
+                public void onChanged() {
+                    // Maybe we did not have capabilities when the current printer was
+                    // selected, but now the selected printer has capabilities. Generate
+                    // a fake selection so the code in the selection change handling takes
+                    // care of updating everything. This way the logic is in one place.
+                    if (mWaitingForPrinterCapabilities) {
+                        mWaitingForPrinterCapabilities = false;
+                        PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
+                        if (printer != null) {
+                            if (printer.getCapabilities() != null) {
+                                final int selectedPosition =
+                                        mDestinationSpinner.getSelectedItemPosition();
+                                mOnItemSelectedListener.onItemSelected(mDestinationSpinner, null,
+                                        selectedPosition, selectedPosition);
+                            }
+                        }
+                    }
+                    updateUi();
+                }
+
+                @Override
+                public void onInvalidated() {
+                    updateUi();
+                }
+            });
             mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
-            mDestinationSpinnerAdapter = new DestinationAdapter();
             mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
             mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
 
@@ -877,7 +931,6 @@
                 @Override
                 public void onClick(View v) {
                     mEditor.confirmPrint();
-                    updateUi();
                     mController.update();
                     showGeneratingPrintJobUi();
                 }
@@ -1001,6 +1054,11 @@
 
         public void confirmPrint() {
             mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
+            PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
+            if (printer != null) {
+                mFavoritePrinters.addPrinter(printer);
+            }
+            updateUi();
         }
 
         public boolean isPreviewConfirmed() {
@@ -1063,8 +1121,8 @@
 
             final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
 
-            if (selectedIndex < 0 || mDestinationSpinnerAdapter.getItem(
-                    selectedIndex).value.getCapabilities() == null) {
+            if (selectedIndex < 0 || ((PrinterInfo) mDestinationSpinnerAdapter.getItem(
+                    selectedIndex)).getCapabilities() == null) {
 
                 // Destination
                 mDestinationSpinner.setEnabled(false);
@@ -1121,16 +1179,12 @@
                 mPrintButton.setEnabled(false);
             } else {
                 PrintAttributes defaultAttributes = mTempPrintAttributes;
-                PrinterInfo printer = mDestinationSpinnerAdapter.getItem(selectedIndex).value;
+                PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
                 PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
                 printer.getCapabilities().getDefaults(defaultAttributes);
 
                 // Destination
-                if (mDestinationSpinnerAdapter.getCount() > 1) {
-                    mDestinationSpinner.setEnabled(true);
-                } else {
-                    mDestinationSpinner.setEnabled(false);
-                }
+                mDestinationSpinner.setEnabled(true);
 
                 // Copies
                 mCopiesEditText.setEnabled(true);
@@ -1159,9 +1213,6 @@
                     if (mediaSizeCount <= 0) {
                         mMediaSizeSpinner.setEnabled(false);
                         mMediaSizeSpinner.setSelection(AdapterView.INVALID_POSITION);
-                    } else if (mediaSizeCount == 1) {
-                        mMediaSizeSpinner.setEnabled(false);
-                        mMediaSizeSpinner.setSelection(0);
                     } else {
                         mMediaSizeSpinner.setEnabled(true);
                         final int selectedMediaSizeIndex = Math.max(mediaSizes.indexOf(
@@ -1210,9 +1261,6 @@
                     if (colorModeCount <= 0) {
                         mColorModeSpinner.setEnabled(false);
                         mColorModeSpinner.setSelection(AdapterView.INVALID_POSITION);
-                    } else if (colorModeCount == 1) {
-                        mColorModeSpinner.setEnabled(false);
-                        mColorModeSpinner.setSelection(0);
                     } else {
                         mColorModeSpinner.setEnabled(true);
                         final int selectedColorModeIndex = Integer.numberOfTrailingZeros(
@@ -1262,9 +1310,6 @@
                     if (orientationCount <= 0) {
                         mOrientationSpinner.setEnabled(false);
                         mOrientationSpinner.setSelection(AdapterView.INVALID_POSITION);
-                    } else if (orientationCount == 1) {
-                        mOrientationSpinner.setEnabled(false);
-                        mOrientationSpinner.setSelection(0);
                     } else {
                         mOrientationSpinner.setEnabled(true);
                         final int selectedOrientationIndex = Integer.numberOfTrailingZeros(
@@ -1336,99 +1381,6 @@
             }
         }
 
-        public void addPrinters(List<PrinterInfo> addedPrinters) {
-            final int addedPrinterCount = addedPrinters.size();
-            for (int i = 0; i < addedPrinterCount; i++) {
-                PrinterInfo addedPrinter = addedPrinters.get(i);
-                boolean duplicate = false;
-                final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
-                for (int j = 0; j < existingPrinterCount; j++) {
-                    PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
-                    if (addedPrinter.getId().equals(existingPrinter.getId())) {
-                        duplicate = true;
-                        break;
-                    }
-                }
-                if (!duplicate) {
-                    mDestinationSpinnerAdapter.add(new SpinnerItem<PrinterInfo>(
-                            addedPrinter, addedPrinter.getName()));
-                } else {
-                    Log.w(LOG_TAG, "Skipping a duplicate printer: " + addedPrinter);
-                }
-            }
-
-            if (mDestinationSpinner.getSelectedItemPosition() == AdapterView.INVALID_POSITION
-                    && mDestinationSpinnerAdapter.getCount() > 0) {
-                mDestinationSpinner.setSelection(0);
-            }
-
-            mEditor.updateUi();
-        }
-
-        public void removePrinters(List<PrinterId> pritnerIds) {
-            final int printerIdCount = pritnerIds.size();
-            for (int i = 0; i < printerIdCount; i++) {
-                PrinterId removedPrinterId = pritnerIds.get(i);
-                boolean removed = false;
-                final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
-                for (int j = 0; j < existingPrinterCount; j++) {
-                    PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
-                    if (removedPrinterId.equals(existingPrinter.getId())) {
-                        mDestinationSpinnerAdapter.remove(mDestinationSpinnerAdapter.getItem(j));
-                        removed = true;
-                        break;
-                    }
-                }
-                if (!removed) {
-                    Log.w(LOG_TAG, "Ignoring not added printer with id: " + removedPrinterId);
-                }
-            }
-
-            if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION
-                    && mDestinationSpinnerAdapter.getCount() == 0) {
-                mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
-            }
-        }
-
-        @SuppressWarnings("unchecked")
-        public void updatePrinters(List<PrinterInfo> pritners) {
-            SpinnerItem<PrinterInfo> selectedItem =
-                    (SpinnerItem<PrinterInfo>) mDestinationSpinner.getSelectedItem();
-            PrinterId selectedPrinterId = (selectedItem != null)
-                    ? selectedItem.value.getId() : null;
-
-            boolean updated = false;
-
-            final int printerCount = pritners.size();
-            for (int i = 0; i < printerCount; i++) {
-                PrinterInfo updatedPrinter = pritners.get(i);
-                final int existingPrinterCount = mDestinationSpinnerAdapter.getCount();
-                for (int j = 0; j < existingPrinterCount; j++) {
-                    PrinterInfo existingPrinter = mDestinationSpinnerAdapter.getItem(j).value;
-                    if (updatedPrinter.getId().equals(existingPrinter.getId())) {
-                        existingPrinter.copyFrom(updatedPrinter);
-                        updated = true;
-                        if (selectedPrinterId != null
-                                && selectedPrinterId.equals(updatedPrinter.getId())) {
-                            // The selected printer was updated. We simulate a fake
-                            // selection to reuse the normal printer change handling.
-                            mOnItemSelectedListener.onItemSelected(mDestinationSpinner,
-                                    mDestinationSpinner.getSelectedView(),
-                                    mDestinationSpinner.getSelectedItemPosition(),
-                                    mDestinationSpinner.getSelectedItemId());
-                            // TODO: This will reset the UI to the defaults for the
-                            // printer. We may need to revisit this.
-                            
-                        }
-                        break;
-                    }
-                }
-            }
-            if (updated) {
-                mDestinationSpinnerAdapter.notifyDataSetChanged();
-            }
-        }
-
         private boolean hasErrors() {
             return mRangeEditText.getError() != null
                     || mCopiesEditText.getError() != null;
@@ -1455,10 +1407,39 @@
             }
         }
 
-        private final class DestinationAdapter extends ArrayAdapter<SpinnerItem<PrinterInfo>> {
+        private final class DestinationAdapter extends BaseAdapter {
+            private final AvailablePrinterProvider mProvider;
 
-            public DestinationAdapter() {
-                super( PrintJobConfigActivity.this, R.layout.spinner_dropdown_item);
+            private final DataSetObserver mObserver = new DataSetObserver() {
+                @Override
+                public void onChanged() {
+                    notifyDataSetChanged();
+                }
+
+                @Override
+                public void onInvalidated() {
+                    notifyDataSetInvalidated();
+                }
+            };
+
+            public DestinationAdapter(AvailablePrinterProvider provider) {
+                mProvider = provider;
+                mProvider.registerObserver(mObserver);
+            }
+
+            @Override
+            public int getCount() {
+                return mProvider.getItemCount();
+            }
+
+            @Override
+            public Object getItem(int position) {
+                return mProvider.getItemAt(position);
+            }
+
+            @Override
+            public long getItemId(int position) {
+                return position;
             }
 
             @Override
@@ -1474,7 +1455,7 @@
                             R.layout.spinner_dropdown_item, parent, false);
                 }
 
-                PrinterInfo printerInfo = getItem(position).value;
+                PrinterInfo printerInfo = mProvider.getItemAt(position);
                 TextView title = (TextView) convertView.findViewById(R.id.title);
                 title.setText(printerInfo.getName());
 
@@ -1495,132 +1476,6 @@
         }
     }
 
-    private static final class PrinterDiscoverySessionObserver
-            extends IPrinterDiscoverySessionObserver.Stub {
-        private static final int MSG_SET_CONTROLLER = 1;
-        private static final int MSG_ON_PRINTERS_ADDED = 2;
-        private static final int MSG_ON_PRINTERS_REMOVED = 3;
-        private static final int MSG_ON_PRINTERS_UPDATED = 4;
-
-        private Handler mHandler;
-        private Editor mEditor;
-        private IPrinterDiscoverySessionController mController;
-
-        @SuppressWarnings("unchecked")
-        public PrinterDiscoverySessionObserver(Editor editor, Looper looper) {
-            mEditor = editor;
-            mHandler = new Handler(looper, null, true) {
-                @Override
-                public void handleMessage(Message message) {
-                    switch (message.what) {
-                        case MSG_SET_CONTROLLER: {
-                            mController = (IPrinterDiscoverySessionController) message.obj;
-                            // TODO: This should be cleaned up
-                            List<PrinterId> printerIds = Collections.emptyList();
-                            try {
-                                mController.open(printerIds);
-                            } catch (RemoteException e) {
-                                Log.e(LOG_TAG, "Error starting printer discovery");
-                            }
-                        } break;
-
-                        case MSG_ON_PRINTERS_ADDED: {
-                            List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
-                            mEditor.addPrinters(printers);
-                        } break;
-
-                        case MSG_ON_PRINTERS_REMOVED: {
-                            List<PrinterId> printerIds = (List<PrinterId>) message.obj;
-                            mEditor.removePrinters(printerIds);
-                        } break;
-
-                        case MSG_ON_PRINTERS_UPDATED: {
-                            List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
-                            mEditor.updatePrinters(printers);
-                        } break;
-                    }
-                }
-            };
-        }
-
-        public void open(List<PrinterId> priorityList) {
-            if (mController != null) {
-                try {
-                    mController.open(priorityList);
-                } catch (RemoteException re) {
-                    Log.e(LOG_TAG, "Error closing printer discovery session", re);
-                }
-            }
-        }
-
-        public void close() {
-            if (mController != null) {
-                try {
-                    mController.close();
-                } catch (RemoteException re) {
-                    Log.e(LOG_TAG, "Error closing printer discovery session", re);
-                }
-            }
-        }
-
-        public void requestPrinterUpdate(PrinterId printerId) {
-            if (mController != null) {
-                try {
-                    mController.requestPrinterUpdate(printerId);
-                } catch (RemoteException re) {
-                    Log.e(LOG_TAG, "Error requestin printer update", re);
-                }
-            }
-        }
-
-        @Override
-        public void setController(IPrinterDiscoverySessionController controller) {
-            synchronized (this) {
-                if (mHandler != null) {
-                    mHandler.obtainMessage(MSG_SET_CONTROLLER, controller)
-                        .sendToTarget();
-                }
-            }
-        }
-
-        @Override
-        public void onPrintersAdded(List<PrinterInfo> printers) {
-            synchronized (this) {
-                if (mHandler != null) {
-                    mHandler.obtainMessage(MSG_ON_PRINTERS_ADDED, printers)
-                        .sendToTarget();
-                }
-            }
-        }
-
-        @Override
-        public void onPrintersRemoved(List<PrinterId> printers) {
-            synchronized (this) {
-                if (mHandler != null) {
-                    mHandler.obtainMessage(MSG_ON_PRINTERS_REMOVED, printers)
-                        .sendToTarget();
-                }
-            }
-        }
-
-        @Override
-        public void onPrintersUpdated(List<PrinterInfo> printers) {
-            synchronized (this) {
-                if (mHandler != null) {
-                    mHandler.obtainMessage(MSG_ON_PRINTERS_UPDATED, printers)
-                        .sendToTarget();
-                }
-            }
-        }
-
-        public void destroy() {
-            synchronized (this) {
-                mHandler = null;
-                mEditor = null;
-            }
-        }
-    }
-
     /**
      * An instance of this class class is intended to be the first focusable
      * in a layout to which the system automatically gives focus. It performs
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
index 1b8b81a..c2cf65e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
@@ -477,7 +477,7 @@
         private static final String ATTR_FITTING_MODE = "fittingMode";
         private static final String ATTR_ORIENTATION = "orientation";
 
-        private static final String ATTR_PRINTER_NAME = "printerName";
+        private static final String ATTR_LOCAL_ID = "printerName";
         private static final String ATTR_SERVICE_NAME = "serviceName";
 
         private static final String ATTR_WIDTH_MILS = "widthMils";
@@ -568,7 +568,7 @@
                     PrinterId printerId = printJob.getPrinterId();
                     if (printerId != null) {
                         serializer.startTag(null, TAG_PRINTER_ID);
-                        serializer.attribute(null, ATTR_PRINTER_NAME, printerId.getLocalId());
+                        serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
                         serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
                                 .flattenToString());
                         serializer.endTag(null, TAG_PRINTER_ID);
@@ -695,13 +695,7 @@
                 Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
                 mStatePersistFile.failWrite(out);
             } finally {
-                if (out != null) {
-                    try {
-                        out.close();
-                    } catch (IOException ioe) {
-                        /* ignore */
-                    }
-                }
+                IoUtils.closeQuietly(out);
             }
         }
 
@@ -733,11 +727,7 @@
             } catch (IndexOutOfBoundsException iobe) {
                 Slog.w(LOG_TAG, "Failed parsing ", iobe);
             } finally {
-                try {
-                    in.close();
-                } catch (IOException ioe) {
-                    /* ignore */
-                }
+                IoUtils.closeQuietly(in);
             }
         }
 
@@ -784,7 +774,7 @@
 
             skipEmptyTextTags(parser);
             if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
-                String localId = parser.getAttributeValue(null, ATTR_PRINTER_NAME);
+                String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
                 ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
                         null, ATTR_SERVICE_NAME));
                 printJob.setPrinterId(new PrinterId(service, localId));
diff --git a/packages/SystemUI/ic_sysbar_internal.psd b/packages/SystemUI/ic_sysbar_internal.psd
deleted file mode 100644
index 929c872..0000000
--- a/packages/SystemUI/ic_sysbar_internal.psd
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/lightning.png b/packages/SystemUI/res/drawable-nodpi/lightning.png
new file mode 100644
index 0000000..29de308
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/lightning.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/quick_settings_tile_battery.xml b/packages/SystemUI/res/layout/quick_settings_tile_battery.xml
index c41e9b9..f3b894c 100644
--- a/packages/SystemUI/res/layout/quick_settings_tile_battery.xml
+++ b/packages/SystemUI/res/layout/quick_settings_tile_battery.xml
@@ -19,14 +19,14 @@
         android:layout_height="match_parent"
         android:layout_gravity="top"
         android:orientation="vertical">
-    <ImageView
+    <com.android.systemui.BatteryMeterView
             android:id="@+id/image"
             android:layout_marginTop="@dimen/qs_tile_margin_above_icon"
             android:layout_marginBottom="@dimen/qs_tile_margin_below_icon"
-            android:layout_width="@dimen/qs_tile_icon_size"
-            android:layout_height="@dimen/qs_tile_icon_size"
+            android:layout_width="22dp"
+            android:layout_height="32dp"
+            android:padding="3dp"
             android:layout_gravity="top|center_horizontal"
-            android:scaleType="centerInside"
             />
     <TextView
             style="@style/TextAppearance.QuickSettings.TileView"
@@ -36,4 +36,4 @@
             android:layout_gravity="top|center_horizontal"
             android:gravity="top|center_horizontal"
             />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/signal_cluster_view.xml b/packages/SystemUI/res/layout/signal_cluster_view.xml
index aab5083..66b06ef 100644
--- a/packages/SystemUI/res/layout/signal_cluster_view.xml
+++ b/packages/SystemUI/res/layout/signal_cluster_view.xml
@@ -20,8 +20,9 @@
 
 <com.android.systemui.statusbar.SignalClusterView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_height="wrap_content"
+    android:layout_height="match_parent"
     android:layout_width="wrap_content"
+    android:gravity="center"
     android:orientation="horizontal"
     >
     <FrameLayout
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index b27536d8..4741cec 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -95,11 +95,13 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     />
-                <ImageView
+                <!-- battery must be padded below by 1px to match assets -->
+                <com.android.systemui.BatteryMeterView
                     android:id="@+id/battery"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:paddingStart="4dip"
+                    android:layout_height="16dp"
+                    android:layout_width="10dp"
+                    android:paddingBottom="1px"
+                    android:layout_marginStart="4dip"
                     />
             </LinearLayout>
     
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 42ec9c5..9b2c127 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi gekoppel"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Soek vir GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Ligging deur GPS gestel"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Liggingversoeke aktief"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Verwyder alle kennisgewings."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Programinligting"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Die skerm sal outomaties draai."</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 41610a6..4aa452d 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi ተያይዟል"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"ለGPS በመፈለግ ላይ"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"በ GPS የተዘጋጀ ሥፍራ"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"ገባሪ የአካባቢ ጥያቄዎች"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"ሁሉንም ማሳወቂያዎች አጽዳ"</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"የመተግበሪያ መረጃ"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"ማያ ገጽ በራስ ሰር ይዞራል።"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 291bcf5..f7f5e37 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi متصل"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"جارٍ البحث عن GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"تم تعيين الموقع بواسطة GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"طلبات الموقع نشطة"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"محو جميع الإشعارات."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"معلومات التطبيق"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"سيتم تدوير الشاشة تلقائيًا."</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index f7f56c0..178238f 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -166,8 +166,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: připojeno"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Vyhledávání satelitů GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Poloha nastavena pomocí systému GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Aktivní žádosti o polohu"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Vymazat všechna oznámení."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informace o aplikaci"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Obrazovka se automaticky otočí."</string>
@@ -204,6 +203,6 @@
     <string name="quick_settings_brightness_dialog_auto_brightness_label" msgid="5064982743784071218">"AUTOMATICKY"</string>
     <string name="status_bar_help_title" msgid="1199237744086469217">"Zde se zobrazují oznámení"</string>
     <string name="status_bar_help_text" msgid="7874607155052076323">"Můžete je kdykoli zobrazit tím, že přejedete prstem dolů.\nPřejedete-li prstem dolů ještě jednou, zobrazí se ovládací prvky systému."</string>
-    <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Panel zobrazíte přejetím přes okraj obrazovky nahoru"</string>
+    <string name="hiding_navigation_confirmation_message" msgid="3227814171674734332">"Panel zobrazíte přejetím přes okraj obrazovky"</string>
     <string name="hiding_navigation_confirmation_message_long" msgid="7854368870786524950">"Systémový panel zobrazíte přejetím přes okraj obrazovky"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index cfd396e..f965773 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -166,8 +166,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi συνδεδεμένο"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Αναζήτηση για GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Ρύθμιση τοποθεσίας με GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Τα αιτήματα τοποθεσίας έχουν ενεργοποιηθεί"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Εκκαθάριση όλων των ειδοποιήσεων."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Πληροφορίες εφαρμογής"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Θα γίνεται αυτόματη περιστροφή της οθόνης."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 017844c..a7d621352 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi connected"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Searching for GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Location set by GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Location requests active"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Clear all notifications."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"App info"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Screen will rotate automatically."</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 4ae8967..a81a2b4 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Con conexión Wi-Fi"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Buscando GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Ubicación definida por GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Solicitudes de ubicación activas"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Borrar todas las notificaciones"</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Información de la aplicación"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"La pantalla girará automáticamente."</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 90a9626..c4fda13 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"WiFi on ühendatud"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS-i otsimine"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS-i määratud asukoht"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Asukoha taotlused on aktiivsed"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Kustuta kõik teatised."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Rakenduse teave"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Ekraani pööramine toimub automaatselt."</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 7fb8a11..a84b9b4 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi متصل شد"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"جستجو برای GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"مکان تنظیم شده توسط GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"درخواست‌های موقعیت مکانی فعال است"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"پاک کردن تمام اعلان‌ها"</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"اطلاعات برنامه"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"صفحه به صورت خودکار می‌چرخد."</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index a5f834d..f2cce06 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wifi yhdistetty"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Haetaan GPS-yhteyttä"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Sijainti määritetty GPS:n avulla"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Sijaintipyynnöt aktiiviset"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Tyhjennä kaikki ilmoitukset."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Sovelluksen tiedot"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Ruutu kääntyy automaattisesti."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index c02fc2b..a1de7a1 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi कनेक्‍ट किया गया"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS को खोजा जा रहा है"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS द्वारा सेट किया गया स्‍थान"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"स्थान अनुरोध सक्रिय"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"सभी सूचनाएं साफ़ करें."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"एप्‍लिकेशन जानकारी"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"स्‍क्रीन स्‍वचालित रूप से घूमेगी."</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 8989d0d..f8ff35d 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi povezan"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Traženje GPS-a"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Lokaciju utvrdio GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Zahtjevi za lokaciju aktivni su"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Brisanje svih obavijesti."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informacije o aplikaciji"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Zaslon će se automatski zakrenuti."</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 567b2fc..118baed 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi csatlakoztatva"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS keresése"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"A GPS beállította a helyet"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Aktív helylekérések"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Minden értesítés törlése"</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Alkalmazásinformáció"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"A képernyő automatikusan forogni fog."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 5dab6a1..ff539be 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -166,8 +166,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi connesso"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Ricerca del GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Posizione stabilita dal GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Richieste di accesso alla posizione attive"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Cancella tutte le notifiche."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Informazioni applicazione"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Lo schermo ruoterà automaticamente."</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index d5becc0..b6b3577 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi disambungkan"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Mencari GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Lokasi ditetapkan oleh GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Permintaan lokasi aktif"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Padamkan semua pemberitahuan."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Maklumat apl"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Skrin akan berputar secara automatik."</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 1216bbf..02a5b45 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Verbonden via wifi"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Zoeken naar GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Locatie bepaald met GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Locatieverzoeken actief"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Alle meldingen wissen."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"App-info"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Scherm wordt automatisch geroteerd."</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 350f1dc..163bc06 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi је повезан"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Тражи се GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Локацију је подесио GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Има активних захтева за локацију"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Обриши сва обавештења."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Информације о апликацији"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Екран ће се аутоматски ротирати."</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index ab06b3f..a0fb0a8 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -162,8 +162,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Mtandao-hewa umeunganishwa"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Inatafuta GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Mahali pamewekwa na GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Maombi ya eneo yanatumika"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Futa arifa zote."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Taarifa ya programu"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Skrini itazunguka kiotomatiki."</string>
diff --git a/packages/SystemUI/res/values-sw600dp-port/refs.xml b/packages/SystemUI/res/values-sw600dp-port/refs.xml
deleted file mode 100644
index 62fb77d..0000000
--- a/packages/SystemUI/res/values-sw600dp-port/refs.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (c) 2013, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
-*/
--->
-<resources>
-    <item type="string" name="hiding_navigation_confirmation_message">@string/hiding_navigation_confirmation_message_long</item>
-</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 6fef062..2552e5c 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"เชื่อมต่อ WiFi แล้ว"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"กำลังค้นหา GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"ตำแหน่งที่กำหนดโดย GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"คำขอตำแหน่งที่มีการใช้งาน"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"ล้างการแจ้งเตือนทั้งหมด"</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"ข้อมูลแอป"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"หน้าจอจะหมุนโดยอัตโนมัติ"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 6dc4e9c..dfc6c7d 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"nakakonekta ang Wi-Fi"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Naghahanap ng GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Lokasyong itinatakda ng GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Aktibo ang mga kahilingan ng lokasyon"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"I-clear ang lahat ng notification."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Impormasyon ng app"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Awtomatikong iikot ang screen."</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 397e23d..a0bb92a 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -166,8 +166,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi 已連線"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"正在搜尋 GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"GPS 已定位"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"有位置資訊要求"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"清除所有通知。"</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"應用程式資訊"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"螢幕會自動旋轉。"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 1efcbcb..53e7db0e 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -164,8 +164,7 @@
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"I-Wi-Fi ixhunyiwe"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"Isesha i-GPS"</string>
     <string name="gps_notification_found_text" msgid="4619274244146446464">"Indawo ihlelwe i-GPS"</string>
-    <!-- no translation found for accessibility_location_active (2427290146138169014) -->
-    <skip />
+    <string name="accessibility_location_active" msgid="2427290146138169014">"Izicelo zendawo ziyasebenza"</string>
     <string name="accessibility_clear_all" msgid="5235938559247164925">"Susa zonke izaziso."</string>
     <string name="status_bar_notification_inspect_item_title" msgid="1163547729015390250">"Ulwazi lohlelo lokusebenza"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"Isikrini sizophenduka ngokuzenzakalela."</string>
diff --git a/packages/SystemUI/res/values/arrays.xml b/packages/SystemUI/res/values/arrays.xml
index cd6aaf6..506722d2 100644
--- a/packages/SystemUI/res/values/arrays.xml
+++ b/packages/SystemUI/res/values/arrays.xml
@@ -40,4 +40,16 @@
         <item>@null</item>
     </array>
 
+    <!-- BatteryMeterView parameters -->
+    <array name="batterymeter_color_levels">
+        <item>4</item>
+        <item>15</item>
+        <item>100</item>
+    </array>
+    <array name="batterymeter_color_values">
+        <item>#FFFF0000</item>
+        <item>#FFFE6600</item>
+        <item>#FF3792B4</item>
+    </array>
+
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4a7d090..04de60f 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -20,6 +20,10 @@
     <drawable name="notification_number_text_color">#ffffffff</drawable>
     <drawable name="ticker_background_color">#ff1d1d1d</drawable>
     <drawable name="status_bar_background">#ff000000</drawable>
+    <color name="status_bar_background_transient">#55000000</color>
+    <color name="status_bar_background_transparent">#00000000</color>
+    <color name="navigation_bar_background_transparent_start">#7f000000</color>
+    <color name="navigation_bar_background_transparent_end">#00000000</color>
     <color name="notification_panel_solid_background">#ff000000</color>
     <drawable name="status_bar_recents_app_thumbnail_background">#88000000</drawable>
     <color name="status_bar_recents_app_label_color">#ffffffff</color>
@@ -30,4 +34,6 @@
     <drawable name="heads_up_notification_bg_pressed">#ff33B5E5</drawable>
     <drawable name="notification_header_bg">#FF000000</drawable>
     <color name="notification_panel_scrim_color">#B0000000</color>
+
+    <color name="batterymeter_frame_color">#FF404040</color>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 33a85c3a..c849aa6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -504,9 +504,6 @@
     <!-- Body of help text shown when the notification panel is pulled down for the very first time. [CHAR LIMIT=NONE] -->
     <string name="status_bar_help_text">Access them anytime by swiping down.\nSwipe down again for system controls.</string>
 
-    <!-- Toast bar message when hiding the navigation bar -->
-    <string name="hiding_navigation_confirmation_message">Swipe edge of screen to reveal bar</string>
-
-    <!-- Longer version of toast bar message when hiding the navigation bar (if room) -->
-    <string name="hiding_navigation_confirmation_message_long">Swipe from edge of screen to reveal system bar</string>
+    <!-- Glyph to be overlaid atop the battery when the level is extremely low. Do not translate. -->
+    <string name="battery_meter_very_low_overlay_symbol">!</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
new file mode 100755
index 0000000..aa4362e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.os.BatteryManager;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class BatteryMeterView extends View {
+    public static final String TAG = BatteryMeterView.class.getSimpleName();
+    public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
+
+    public static final boolean ENABLE_PERCENT = true;
+    public static final boolean SINGLE_DIGIT_PERCENT = false;
+    public static final boolean SHOW_100_PERCENT = false;
+
+    public static final int FULL = 96;
+    public static final int EMPTY = 4;
+
+    int[] mColors;
+
+    boolean mShowPercent = true;
+    Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint;
+    int mButtonHeight;
+    private float mTextHeight, mWarningTextHeight;
+    Drawable mLightning;
+
+    private int mHeight;
+    private int mWidth;
+    private String mWarningString;
+
+    private class BatteryTracker extends BroadcastReceiver {
+        // current battery status
+        int level;
+        String percentStr;
+        int plugType;
+        boolean plugged;
+        int health;
+        int status;
+        String technology;
+        int voltage;
+        int temperature;
+        boolean testmode = false;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+                if (testmode && ! intent.getBooleanExtra("testmode", false)) return;
+
+                level = (int)(100f
+                        * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
+                        / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+
+                plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+                plugged = plugType != 0;
+                health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
+                        BatteryManager.BATTERY_HEALTH_UNKNOWN);
+                status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
+                        BatteryManager.BATTERY_STATUS_UNKNOWN);
+                technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
+                voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
+                temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
+
+                setContentDescription(
+                        context.getString(R.string.accessibility_battery_level, level));
+                postInvalidate();
+            } else if (action.equals(ACTION_LEVEL_TEST)) {
+                testmode = true;
+                post(new Runnable() {
+                    int curLevel = 0;
+                    int incr = 1;
+                    int saveLevel = level;
+                    int savePlugged = plugType;
+                    Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
+                    @Override
+                    public void run() {
+                        if (curLevel < 0) {
+                            testmode = false;
+                            dummy.putExtra("level", saveLevel);
+                            dummy.putExtra("plugged", savePlugged);
+                            dummy.putExtra("testmode", false);
+                        } else {
+                            dummy.putExtra("level", curLevel);
+                            dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC : 0);
+                            dummy.putExtra("testmode", true);
+                        }
+                        getContext().sendBroadcast(dummy);
+
+                        if (!testmode) return;
+
+                        curLevel += incr;
+                        if (curLevel == 100) {
+                            incr *= -1;
+                        }
+                        postDelayed(this, 200);
+                    }
+                });
+            }
+        }
+    }
+
+    BatteryTracker mTracker = new BatteryTracker();
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        filter.addAction(ACTION_LEVEL_TEST);
+        getContext().registerReceiver(mTracker, filter);
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        getContext().unregisterReceiver(mTracker);
+    }
+
+    public BatteryMeterView(Context context) {
+        this(context, null, 0);
+    }
+
+    public BatteryMeterView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final Resources res = context.getResources();
+        TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels);
+        TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values);
+
+        final int N = levels.length();
+        mColors = new int[2*N];
+        for (int i=0; i<N; i++) {
+            mColors[2*i] = levels.getInt(i, 0);
+            mColors[2*i+1] = colors.getColor(i, 0);
+        }
+
+        mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt(
+                context.getContentResolver(), "status_bar_show_battery_percent", 0);
+
+        mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol);
+
+        mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mFramePaint.setColor(res.getColor(R.color.batterymeter_frame_color));
+        mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mBatteryPaint.setColor(0xFF00FF00); // will be replaced by something from mColors
+
+        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mTextPaint.setColor(0xFFFFFFFF);
+        Typeface font = Typeface.create("sans-serif-condensed", Typeface.NORMAL);
+        mTextPaint.setTypeface(font);
+        mTextPaint.setTextAlign(Paint.Align.CENTER);
+
+        mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mWarningTextPaint.setColor(mColors[1]);
+        font = Typeface.create("sans-serif", Typeface.BOLD);
+        mWarningTextPaint.setTypeface(font);
+        mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
+
+        mLightning = getResources().getDrawable(R.drawable.lightning);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mHeight = h;
+        mWidth = w;
+        mWarningTextPaint.setTextSize(h * 0.75f);
+        mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent;
+    }
+
+    private int getColorForLevel(int percent) {
+        int thresh, color = 0;
+        for (int i=0; i<mColors.length; i+=2) {
+            thresh = mColors[i];
+            color = mColors[i+1];
+            if (percent <= thresh) return color;
+        }
+        return color;
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        final int level = mTracker.level;
+        float drawFrac = (float) level / 100f;
+        final int pt = getPaddingTop();
+        final int pl = getPaddingLeft();
+        final int pr = getPaddingRight();
+        final int pb = getPaddingBottom();
+        int height = mHeight - pt - pb;
+        int width = mWidth - pl - pr;
+
+        mButtonHeight = (int) (height * 0.12f);
+
+        final RectF frame = new RectF(0, 0, width, height);
+        frame.offset(pl, pt);
+
+        // Log.v("BatteryGauge", String.format("canvas: %dx%d frame: %s",
+        // c.getWidth(), c.getHeight(), frame.toString()));
+
+        final RectF buttonframe = new RectF(
+                frame.left + width * 0.25f,
+                frame.top,
+                frame.right - width * 0.25f,
+                frame.top + mButtonHeight);
+
+        frame.top += mButtonHeight;
+
+        // first, draw the battery shape
+        c.drawRect(frame, mFramePaint);
+
+        // fill 'er up
+        final int pct = mTracker.level;
+        final int color = getColorForLevel(pct);
+        mBatteryPaint.setColor(color);
+
+        if (level >= FULL) {
+            drawFrac = 1f;
+        } else if (level <= EMPTY) {
+            drawFrac = 0f;
+        }
+
+        c.drawRect(buttonframe,
+                drawFrac == 1f ? mBatteryPaint : mFramePaint);
+
+        RectF clip = new RectF(frame);
+        clip.top += (frame.height() * (1f - drawFrac));
+
+        c.save(Canvas.CLIP_SAVE_FLAG);
+        c.clipRect(clip);
+        c.drawRect(frame, mBatteryPaint);
+        c.restore();
+
+        if (level <= EMPTY) {
+            final float x = mWidth * 0.5f;
+            final float y = (mHeight + mWarningTextHeight) * 0.48f;
+            c.drawText(mWarningString, x, y, mWarningTextPaint);
+        } else if (mTracker.plugged) {
+            final Rect r = new Rect(
+                    (int)frame.left + width / 4,  (int)frame.top + height / 5,
+                    (int)frame.right - width / 4, (int)frame.bottom - height / 6);
+            mLightning.setBounds(r);
+            mLightning.draw(c);
+        } else if (mShowPercent && !(mTracker.level == 100 && !SHOW_100_PERCENT)) {
+            mTextPaint.setTextSize(height *
+                    (SINGLE_DIGIT_PERCENT ? 0.75f
+                            : (mTracker.level == 100 ? 0.38f : 0.5f)));
+            mTextHeight = -mTextPaint.getFontMetrics().ascent;
+
+            final String str = String.valueOf(SINGLE_DIGIT_PERCENT ? (pct/10) : pct);
+            final float x = mWidth * 0.5f;
+            final float y = (mHeight + mTextHeight) * 0.47f;
+            c.drawText(str,
+                    x,
+                    y,
+                    mTextPaint);
+
+//            Paint pt = new Paint();
+//            pt.setStrokeWidth(1f);
+//            pt.setStyle(Paint.Style.STROKE);
+//            pt.setColor(0xFFFF0000);
+//            c.drawRect(x, y-mTextHeight, x+tw, y, pt);
+//
+//            Slog.v(TAG, "tw=" + tw + " th=" + mTextHeight);
+//
+//            pt.setColor(0xFFFF00FF);
+//            c.drawRect(1, 1, mWidth, mHeight, pt);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
new file mode 100644
index 0000000..74b14b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.android.systemui.R;
+
+public class BarTransitions {
+
+    public static final int MODE_NORMAL = 0;
+    public static final int MODE_TRANSIENT = 1;
+    public static final int MODE_TRANSPARENT = 2;
+
+    private final View mTarget;
+    private final Drawable mOpaque;
+    private final Drawable mTransient;
+
+    private Drawable mTransparent;
+    private int mMode;
+
+    public BarTransitions(Context context, View target, Drawable transparent) {
+        mTarget = target;
+        final Resources res = context.getResources();
+        mOpaque = new ColorDrawable(res.getColor(R.drawable.status_bar_background));
+        mTransient = new ColorDrawable(res.getColor(R.color.status_bar_background_transient));
+        mTransparent = transparent;
+    }
+
+    public void setTransparent(Drawable transparent) {
+        mTransparent = transparent;
+        if (mMode == MODE_TRANSPARENT) {
+            transitionTo(MODE_TRANSPARENT);
+        }
+    }
+
+    public void transitionTo(int mode) {
+        mMode = mode;
+        if (!ActivityManager.isHighEndGfx()) return;
+        Drawable background = mode == MODE_TRANSIENT ? mTransient
+                : mode == MODE_TRANSPARENT ? mTransparent
+                : mOpaque;
+        mTarget.setBackground(background);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index de33b87..131713f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -25,6 +25,8 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.GradientDrawable.Orientation;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ServiceManager;
@@ -79,6 +81,9 @@
 
     private DelegateViewHelper mDelegateHelper;
     private DeadZone mDeadZone;
+    private final BarTransitions mBarTransitions;
+    private final Drawable mTransparent;
+    private final Drawable mTransparentVertical;
 
     // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
     final static boolean WORKAROUND_INVALID_LAYOUT = true;
@@ -107,6 +112,37 @@
         }
     }
 
+    public NavigationBarView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mHidden = false;
+
+        mDisplay = ((WindowManager)context.getSystemService(
+                Context.WINDOW_SERVICE)).getDefaultDisplay();
+        mBarService = IStatusBarService.Stub.asInterface(
+                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+
+        final Resources res = mContext.getResources();
+        mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
+        mVertical = false;
+        mShowMenu = false;
+        mDelegateHelper = new DelegateViewHelper(this);
+
+        getIcons(res);
+
+        final int[] gradientColors = new int[] {
+                res.getColor(R.color.navigation_bar_background_transparent_start),
+                res.getColor(R.color.navigation_bar_background_transparent_end)
+        };
+        mTransparent = new GradientDrawable(Orientation.BOTTOM_TOP, gradientColors);
+        mTransparentVertical = new GradientDrawable(Orientation.RIGHT_LEFT, gradientColors);
+        mBarTransitions = new BarTransitions(context, this, mTransparent);
+    }
+
+    public BarTransitions getBarTransitions() {
+        return mBarTransitions;
+    }
+
     public void setDelegateView(View view) {
         mDelegateHelper.setDelegateView(view);
     }
@@ -155,25 +191,6 @@
         return mCurrentView.findViewById(R.id.search_light);
     }
 
-    public NavigationBarView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        mHidden = false;
-
-        mDisplay = ((WindowManager)context.getSystemService(
-                Context.WINDOW_SERVICE)).getDefaultDisplay();
-        mBarService = IStatusBarService.Stub.asInterface(
-                ServiceManager.getService(Context.STATUS_BAR_SERVICE));
-
-        final Resources res = mContext.getResources();
-        mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
-        mVertical = false;
-        mShowMenu = false;
-        mDelegateHelper = new DelegateViewHelper(this);
-
-        getIcons(res);
-    }
-
     private void getIcons(Resources res) {
         mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back);
         mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land);
@@ -406,6 +423,7 @@
         }
 
         setNavigationIconHints(mNavigationIconHints, true);
+        mBarTransitions.setTransparent(mVertical ? mTransparentVertical : mTransparent);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index d0a6385..b78239b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -118,7 +118,7 @@
             int i = 0;
             float totalweight = 0f;
             float weight = 10f;
-            for (final Iterator<MotionEventCopy> iter = mEventBuf.descendingIterator();
+            for (final Iterator<MotionEventCopy> iter = mEventBuf.iterator();
                     iter.hasNext();) {
                 final MotionEventCopy event = iter.next();
                 if (last != null) {
@@ -126,13 +126,22 @@
                     final float dx = (event.x - last.x);
                     final float dy = (event.y - last.y);
                     if (FlingTracker.DEBUG) {
-                        Log.v("FlingTracker", String.format("   [%d] dx=%.1f dy=%.1f dt=%.0f vx=%.1f vy=%.1f",
-                                i,
+                        Log.v("FlingTracker", String.format(
+                                "   [%d] (t=%d %.1f,%.1f) dx=%.1f dy=%.1f dt=%f vx=%.1f vy=%.1f",
+                                i, event.t, event.x, event.y,
                                 dx, dy, dt,
                                 (dx/dt),
                                 (dy/dt)
                                 ));
                     }
+                    if (event.t == last.t) {
+                        // Really not sure what to do with events that happened at the same time,
+                        // so we'll skip subsequent events.
+                        if (DEBUG_NAN) {
+                            Log.v("FlingTracker", "skipping simultaneous event at t=" + event.t);
+                        }
+                        continue;
+                    }
                     mVX += weight * dx / dt;
                     mVY += weight * dy / dt;
                     totalweight += weight;
@@ -158,18 +167,18 @@
             }
         }
         public float getXVelocity() {
-            if (Float.isNaN(mVX)) {
+            if (Float.isNaN(mVX) || Float.isInfinite(mVX)) {
                 if (DEBUG_NAN) {
-                    Log.v("FlingTracker", "warning: vx=NaN");
+                    Log.v("FlingTracker", "warning: vx=" + mVX);
                 }
                 mVX = 0;
             }
             return mVX;
         }
         public float getYVelocity() {
-            if (Float.isNaN(mVY)) {
+            if (Float.isNaN(mVY) || Float.isInfinite(mVX)) {
                 if (DEBUG_NAN) {
-                    Log.v("FlingTracker", "warning: vx=NaN");
+                    Log.v("FlingTracker", "warning: vx=" + mVY);
                 }
                 mVY = 0;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index c914a34..62be5d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_NORMAL;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSIENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -57,7 +61,6 @@
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
-import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewPropertyAnimator;
@@ -72,7 +75,7 @@
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
 import android.widget.TextView;
-import android.widget.Toast;
+
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
@@ -129,14 +132,7 @@
 
     private static final int STATUS_OR_NAV_TRANSIENT =
             View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
-    private static final int TRANSIENT_NAV_HIDING =
-            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT;
     private static final long AUTOHIDE_TIMEOUT_MS = 3000;
-    private static final float TRANSPARENT_ALPHA = 0.7f;
-
-    private static final int BAR_MODE_NORMAL = 0;
-    private static final int BAR_MODE_TRANSIENT = 1;
-    private static final int BAR_MODE_TRANSPARENT = 2;
 
     // fling gesture tuning parameters, scaled to display density
     private float mSelfExpandVelocityPx; // classic value: 2000px/s
@@ -314,37 +310,6 @@
         }
     };
 
-    private Toast mHidingNavigationConfirmation;
-    private boolean mHidingNavigationConfirmationDismissed;
-
-    private final View.OnTouchListener mDismissHidingNavigationConfirmationOnTouchOutside =
-            new View.OnTouchListener() {
-                @Override
-                public boolean onTouch(View v, MotionEvent event) {
-                    if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
-                        dismissHidingNavigationConfirmation();
-                    }
-                    return false;
-                }
-            };
-
-    private final Runnable mHidingNavigationConfirmationAction = new Runnable() {
-        @Override
-        public void run() {
-            if (mHidingNavigationConfirmation != null) {
-                final boolean isGloballyConfirmed = Prefs.read(mContext)
-                        .getBoolean(Prefs.HIDING_NAVIGATION_CONFIRMED, false);
-                if (!isGloballyConfirmed) {
-                    // user pressed button, consider this a confirmation
-                    Prefs.edit(mContext)
-                            .putBoolean(Prefs.HIDING_NAVIGATION_CONFIRMED, true)
-                            .apply();
-                }
-                dismissHidingNavigationConfirmation();
-            }
-        }
-    };
-
     private boolean mAutohideSuspended;
 
     private final Runnable mAutohide = new Runnable() {
@@ -542,7 +507,6 @@
         // Other icons
         mLocationController = new LocationController(mContext); // will post a notification
         mBatteryController = new BatteryController(mContext);
-        mBatteryController.addIconView((ImageView)mStatusBarView.findViewById(R.id.battery));
         mNetworkController = new NetworkController(mContext);
         mBluetoothController = new BluetoothController(mContext);
         mRotationLockController = new RotationLockController(mContext);
@@ -1939,91 +1903,42 @@
             }
 
             // update status bar mode
-            int sbMode = updateBarMode(oldVal, newVal, mStatusBarView,
+            int sbMode = updateBarMode(oldVal, newVal, mStatusBarView.getBarTransitions(),
                     View.STATUS_BAR_TRANSIENT, View.SYSTEM_UI_FLAG_TRANSPARENT_STATUS);
 
             // update navigation bar mode
-            int nbMode = updateBarMode(oldVal, newVal, mNavigationBarView,
+            int nbMode = updateBarMode(oldVal, newVal, mNavigationBarView.getBarTransitions(),
                     View.NAVIGATION_BAR_TRANSIENT, View.SYSTEM_UI_FLAG_TRANSPARENT_NAVIGATION);
 
             if (sbMode != -1 || nbMode != -1) {
                 // update transient bar autohide
-                if (sbMode == BAR_MODE_TRANSIENT || nbMode == BAR_MODE_TRANSIENT) {
+                if (sbMode == MODE_TRANSIENT || nbMode == MODE_TRANSIENT) {
                     scheduleAutohide();
                 } else {
                     cancelAutohide();
                 }
             }
 
-            // update hiding navigation confirmation
-            if (mNavigationBarView != null) {
-                boolean oldShowConfirm = (oldVal & TRANSIENT_NAV_HIDING) == TRANSIENT_NAV_HIDING;
-                boolean newShowConfirm = (newVal & TRANSIENT_NAV_HIDING) == TRANSIENT_NAV_HIDING;
-                if (!oldShowConfirm && newShowConfirm) {
-                    mHidingNavigationConfirmationDismissed = false;
-                }
-                setHidingNavigationConfirmationVisible(newShowConfirm);
-            }
-
             // send updated sysui visibility to window manager
             notifyUiVisibilityChanged(mSystemUiVisibility);
         }
     }
 
-    private int updateBarMode(int oldVis, int newVis, View view,
+    private int updateBarMode(int oldVis, int newVis, BarTransitions transitions,
             int transientFlag, int transparentFlag) {
         final int oldMode = barMode(oldVis, transientFlag, transparentFlag);
         final int newMode = barMode(newVis, transientFlag, transparentFlag);
         if (oldMode == newMode) {
             return -1; // no mode change
         }
-        setTransparent(view, newMode != BAR_MODE_NORMAL);
+        transitions.transitionTo(newMode);
         return newMode;
     }
 
     private int barMode(int vis, int transientFlag, int transparentFlag) {
-        return (vis & transientFlag) != 0 ? BAR_MODE_TRANSIENT
-                : (vis & transparentFlag) != 0 ? BAR_MODE_TRANSPARENT
-                : BAR_MODE_NORMAL;
-    }
-
-    private void dismissHidingNavigationConfirmation() {
-        if (mHidingNavigationConfirmation != null) {
-            mHidingNavigationConfirmationDismissed = true;
-            mHidingNavigationConfirmation.cancel();
-            mHidingNavigationConfirmation = null;
-        }
-    }
-
-    private void setHidingNavigationConfirmationVisible(boolean visible) {
-        if (DEBUG) Log.d(TAG, "setHidingNavigationConfirmationVisible " + visible);
-        if (visible &&
-                mHidingNavigationConfirmation == null && !mHidingNavigationConfirmationDismissed) {
-            // create the confirmation toast bar
-            int msg = R.string.hiding_navigation_confirmation_message;
-            mHidingNavigationConfirmation = Toast.makeBar(mContext, msg, Toast.LENGTH_INFINITE)
-                    .setAction(com.android.internal.R.string.ok,
-                            mHidingNavigationConfirmationAction);
-            View v = mHidingNavigationConfirmation.getView();
-            v.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-            boolean isGloballyConfirmed = Prefs.read(mContext)
-                    .getBoolean(Prefs.HIDING_NAVIGATION_CONFIRMED, false);
-            if (isGloballyConfirmed) {
-                // dismiss on outside touch if globally confirmed
-                v.setOnTouchListener(mDismissHidingNavigationConfirmationOnTouchOutside);
-            }
-            // position at the bottom like normal toasts, but use top gravity
-            // to avoid jumping around when showing/hiding the nav bar
-            v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-            int offsetY = mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.toast_y_offset);
-            mHidingNavigationConfirmation.setGravity(Gravity.TOP,
-                    0, mCurrentDisplaySize.y - v.getMeasuredHeight() / 2 - offsetY);
-            // show the confirmation
-            mHidingNavigationConfirmation.show();
-        } else if (!visible) {
-            dismissHidingNavigationConfirmation();
-        }
+        return (vis & transientFlag) != 0 ? MODE_TRANSIENT
+                : (vis & transparentFlag) != 0 ? MODE_TRANSPARENT
+                : MODE_NORMAL;
     }
 
     @Override
@@ -2063,13 +1978,6 @@
         mHandler.postDelayed(mAutohide, 25);
     }
 
-    private void setTransparent(View view, boolean transparent) {
-        float alpha = transparent ? TRANSPARENT_ALPHA : 1;
-        if (DEBUG) Log.d(TAG, "Setting " + (view == mStatusBarView ? "status bar" :
-                view == mNavigationBarView ? "navigation bar" : "view") +  " alpha to " + alpha);
-        view.setAlpha(alpha);
-    }
-
     private void setStatusBarLowProfile(boolean lightsOut) {
         if (mLightsOutAnimation == null) {
             final View notifications = mStatusBarView.findViewById(R.id.notification_icon_area);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 1554e2c..ea84c5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -17,10 +17,10 @@
 package com.android.systemui.statusbar.phone;
 
 import android.app.ActivityManager;
-import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.EventLog;
 import android.util.Log;
@@ -46,6 +46,7 @@
     PanelView mLastFullyOpenedPanel = null;
     PanelView mNotificationPanel, mSettingsPanel;
     private boolean mShouldFade;
+    private final BarTransitions mBarTransitions;
 
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -59,6 +60,12 @@
             mSettingsPanelDragzoneFrac = 0f;
         }
         mFullWidthNotifications = mSettingsPanelDragzoneFrac <= 0f;
+        final Drawable transparent = res.getDrawable(R.color.status_bar_background_transparent);
+        mBarTransitions = new BarTransitions(context, this, transparent);
+    }
+
+    public BarTransitions getBarTransitions() {
+        return mBarTransitions;
     }
 
     public void setBar(PhoneStatusBar bar) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index 5233f42..5f034a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -494,7 +494,9 @@
         }
 
         // Battery
-        final QuickSettingsBasicTile batteryTile = new QuickSettingsBasicTile(mContext);
+        final QuickSettingsTileView batteryTile = (QuickSettingsTileView)
+                inflater.inflate(R.layout.quick_settings_tile, parent, false);
+        batteryTile.setContent(R.layout.quick_settings_tile_battery, inflater);
         batteryTile.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -506,9 +508,6 @@
             public void refreshView(QuickSettingsTileView unused, State state) {
                 QuickSettingsModel.BatteryState batteryState =
                         (QuickSettingsModel.BatteryState) state;
-                Drawable d = batteryState.pluggedIn
-                        ? mChargingBatteryLevels
-                        : mBatteryLevels;
                 String t;
                 if (batteryState.batteryLevel == 100) {
                     t = mContext.getString(R.string.quick_settings_battery_charged_label);
@@ -519,9 +518,7 @@
                         : mContext.getString(R.string.status_bar_settings_battery_meter_format,
                                 batteryState.batteryLevel);
                 }
-                batteryTile.setImageDrawable(d);
-                batteryTile.getImageView().setImageLevel(batteryState.batteryLevel);
-                batteryTile.setText(t);
+                ((TextView)batteryTile.findViewById(R.id.text)).setText(t);
                 batteryTile.setContentDescription(
                         mContext.getString(R.string.accessibility_quick_settings_battery, t));
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
index 91ddf0f..c2ffff8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
@@ -53,7 +53,6 @@
     private StatusBarManager mStatusBarManager;
 
     private boolean mAreActiveLocationRequests;
-    private boolean mIsAirplaneMode;
 
     private ArrayList<LocationSettingsChangeCallback> mSettingsChangeCallbacks =
             new ArrayList<LocationSettingsChangeCallback>();
@@ -76,9 +75,6 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
-        // Listen for a change in the airplane mode setting so we can defensively turn off the
-        // high power location icon when radios are disabled.
-        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         context.registerReceiver(this, filter);
 
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
@@ -100,7 +96,6 @@
 
         // Examine the current location state and initialize the status view.
         updateActiveLocationRequests();
-        updateAirplaneMode();
         refreshViews();
     }
 
@@ -126,12 +121,9 @@
             return;
         }
         final ContentResolver cr = mContext.getContentResolver();
-        Settings.Secure.setLocationProviderEnabled(
-                cr, LocationManager.GPS_PROVIDER, enabled);
-        // When enabling the NETWORK_PROVIDER, a user consent dialog will pop up, and the
-        // setting won't actually be enabled until the user accepts the agreement.
-        Settings.Secure.setLocationProviderEnabled(
-                cr, LocationManager.NETWORK_PROVIDER, enabled);
+        // When enabling location, a user consent dialog will pop up, and the
+        // setting won't be fully enabled until the user accepts the agreement.
+        Settings.Secure.setLocationMasterSwitchEnabled(cr, enabled);
     }
 
     /**
@@ -139,11 +131,7 @@
      */
     public boolean isLocationEnabled() {
         ContentResolver contentResolver = mContext.getContentResolver();
-        boolean isGpsEnabled = Settings.Secure.isLocationProviderEnabled(
-                contentResolver, LocationManager.GPS_PROVIDER);
-        boolean isNetworkEnabled = Settings.Secure.isLocationProviderEnabled(
-                contentResolver, LocationManager.NETWORK_PROVIDER);
-       return isGpsEnabled || isNetworkEnabled;
+        return Settings.Secure.isLocationMasterSwitchEnabled(contentResolver);
     }
 
     /**
@@ -177,11 +165,9 @@
         return false;
     }
 
-    // Updates the status view based on the current state of location requests and airplane mode.
+    // Updates the status view based on the current state of location requests.
     private void refreshViews() {
-        // The airplane mode check is defensive - there shouldn't be any active high power
-        // location requests when airplane mode is on.
-        if (!mIsAirplaneMode && mAreActiveLocationRequests) {
+        if (mAreActiveLocationRequests) {
             mStatusBarManager.setIcon(LOCATION_STATUS_ICON_PLACEHOLDER, LOCATION_STATUS_ICON_ID, 0,
                     mContext.getString(R.string.accessibility_location_active));
         } else {
@@ -198,25 +184,11 @@
         }
     }
 
-    // Reads the airplane mode setting and updates the status view if necessary.
-    private void updateAirplaneMode() {
-        boolean wasAirplaneMode = mIsAirplaneMode;
-        // TODO This probably warrants a utility method in Settings.java.
-        mIsAirplaneMode = (Settings.Global.getInt(
-                mContext.getContentResolver(),
-                Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
-        if (mIsAirplaneMode != wasAirplaneMode) {
-            refreshViews();
-        }
-    }
-
     @Override
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
         if (LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action)) {
             updateActiveLocationRequests();
-        } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
-            updateAirplaneMode();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
index 3d51f20..16a92ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
@@ -25,8 +25,6 @@
     public static final String SHOWN_COMPAT_MODE_HELP = "shown_compat_mode_help";
     public static final String SHOWN_QUICK_SETTINGS_HELP = "shown_quick_settings_help";
 
-    public static final String HIDING_NAVIGATION_CONFIRMED = "hiding_navigation_confirmed";
-
     public static SharedPreferences read(Context context) {
         return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
     }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index ae7120f..9da4357 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -558,6 +558,7 @@
     private static final int TRANSIENT_BAR_HIDING = 2;
     private int mStatusTransientBar;
     private int mNavigationTransientBar;
+    private TransientNavigationConfirmation mTransientNavigationConfirmation;
 
     private SystemGesturesPointerEventListener mSystemGestures;
 
@@ -942,6 +943,7 @@
                         }
                     }
                 });
+        mTransientNavigationConfirmation = new TransientNavigationConfirmation(mContext);
         mWindowManagerFuncs.registerPointerEventListener(mSystemGestures);
 
         mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
@@ -3173,11 +3175,12 @@
                             = mOverscanScreenTop + mOverscanScreenHeight;
                 } else if (mCanHideNavigationBar
                         && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
-                        && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
-                        && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+                        && (attrs.type == TYPE_TOAST
+                            || (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
                     // Asking for layout as if the nav bar is hidden, lets the
                     // application extend into the unrestricted screen area.  We
-                    // only do this for application windows to ensure no window that
+                    // only do this for application windows (or toasts) to ensure no window that
                     // can be above the nav bar can do this.
                     // XXX This assumes that an app asking for this will also
                     // ask for layout in only content.  We can't currently figure out
@@ -3879,6 +3882,9 @@
             case KeyEvent.KEYCODE_POWER: {
                 result &= ~ACTION_PASS_TO_USER;
                 if (down) {
+                    if (isScreenOn && isNavigationBarTransient(mLastSystemUiFlags)) {
+                        mTransientNavigationConfirmation.unconfirmLastPackage();
+                    }
                     if (isScreenOn && !mPowerKeyTriggered
                             && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
                         mPowerKeyTriggered = true;
@@ -5019,7 +5025,7 @@
         if (mForcingShowNavBar && mFocusedWindow.getSurfaceLayer() < mForcingShowNavBarLayer) {
             tmpVisibility &= ~View.SYSTEM_UI_CLEARABLE_FLAGS;
         }
-        final int visibility = updateTransientBarsLw(tmpVisibility);
+        final int visibility = updateTransientBarsLw(mLastSystemUiFlags, tmpVisibility);
         final int diff = visibility ^ mLastSystemUiFlags;
         final boolean needsMenu = mFocusedWindow.getNeedsMenuLw(mTopFullscreenOpaqueWindowState);
         if (diff == 0 && mLastFocusNeedsMenu == needsMenu
@@ -5047,7 +5053,7 @@
         return diff;
     }
 
-    private int updateTransientBarsLw(int vis) {
+    private int updateTransientBarsLw(int oldVis, int vis) {
         if (ImmersiveModeTesting.enabled) {
             vis = ImmersiveModeTesting.applyForced(mFocusedWindow, vis);
         }
@@ -5059,9 +5065,10 @@
                     | View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT;
             vis = (vis & ~flags) | (mLastSystemUiFlags & flags);
         }
-        boolean transientAllowed = (vis & View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT) != 0;
         if (mStatusTransientBar == TRANSIENT_BAR_SHOWING) {
             // status transient bar requested
+            boolean transientAllowed =
+                    (vis & View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT) != 0;
             boolean hideStatusBarWM =
                     (mFocusedWindow.getAttrs().flags
                             & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
@@ -5092,13 +5099,16 @@
                 }
             }
         }
+        boolean oldTransientNav = isNavigationBarTransient(oldVis);
+        boolean isTransientNav = isNavigationBarTransient(vis);
+        if (mFocusedWindow != null && oldTransientNav != isTransientNav) {
+            final int uid = getCurrentUserId();
+            final String pkg = mFocusedWindow.getOwningPackage();
+            mTransientNavigationConfirmation.transientNavigationChanged(uid, pkg, isTransientNav);
+        }
         if (mNavigationTransientBar == TRANSIENT_BAR_SHOWING) {
             // navigation transient bar requested
-            boolean hideNavigationBarSysui =
-                    (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
-            boolean transientNavigationBarAllowed =
-                    mNavigationBar != null && hideNavigationBarSysui && transientAllowed;
-            if (!transientNavigationBarAllowed) {
+            if (!isTransientNav) {
                 mNavigationTransientBar = TRANSIENT_BAR_NONE;
             } else {
                 // show navigation transient bar
@@ -5112,6 +5122,12 @@
         return vis;
     }
 
+    private boolean isNavigationBarTransient(int vis) {
+        return mNavigationBar != null
+                && (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
+                && (vis & View.SYSTEM_UI_FLAG_ALLOW_TRANSIENT) != 0;
+    }
+
     private boolean setBarShowingLw(WindowState win, final boolean show) {
         final int window =
                   win == mStatusBar ? StatusBarManager.WINDOW_STATUS_BAR
diff --git a/policy/src/com/android/internal/policy/impl/TransientNavigationConfirmation.java b/policy/src/com/android/internal/policy/impl/TransientNavigationConfirmation.java
new file mode 100644
index 0000000..3c4f092
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/TransientNavigationConfirmation.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.widget.Toast;
+
+import com.android.internal.R;
+
+/**
+ *  Helper to manage showing/hiding a confirmation prompt when the transient navigation bar
+ *  is hidden.
+ */
+public class TransientNavigationConfirmation {
+    private static final String TAG = "TransientNavigationConfirmation";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final H mHandler;
+    private final ArraySet<String> mConfirmedUserPackages = new ArraySet<String>();
+    private final long mShowDelayMs;
+
+    private Toast mToast;
+    private String mLastUserPackage;
+
+    public TransientNavigationConfirmation(Context context) {
+        mContext = context;
+        mHandler = new H();
+        mShowDelayMs = getNavBarExitDuration() * 3;
+    }
+
+    private long getNavBarExitDuration() {
+        Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
+        return exit != null ? exit.getDuration() : 0;
+    }
+
+    public void transientNavigationChanged(int userId, String pkg, boolean isNavTransient) {
+        if (pkg == null) {
+            return;
+        }
+        String userPkg = userId + ":" + pkg;
+        mHandler.removeMessages(H.SHOW);
+        if (isNavTransient) {
+            mLastUserPackage = userPkg;
+            if (!mConfirmedUserPackages.contains(userPkg)) {
+                if (DEBUG) Slog.d(TAG, "Showing transient navigation confirmation for " + userPkg);
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(H.SHOW, userPkg), mShowDelayMs);
+            }
+        } else {
+            mLastUserPackage = null;
+            if (DEBUG) Slog.d(TAG, "Hiding transient navigation confirmation for " + userPkg);
+            mHandler.sendEmptyMessage(H.HIDE);
+        }
+    }
+
+    public void unconfirmLastPackage() {
+        if (mLastUserPackage != null) {
+            if (DEBUG) Slog.d(TAG, "Unconfirming transient navigation for " + mLastUserPackage);
+            mConfirmedUserPackages.remove(mLastUserPackage);
+        }
+    }
+
+    private void handleHide() {
+        if (mToast != null) {
+            mToast.cancel();
+            mToast = null;
+        }
+    }
+
+    private void handleShow(String userPkg) {
+        // create the confirmation toast bar
+        final int msg = R.string.transient_navigation_confirmation;
+        mToast = Toast.makeBar(mContext, msg, Toast.LENGTH_INFINITE);
+        mToast.setAction(R.string.ok, confirmAction(userPkg));
+
+        // we will be hiding the nav bar, so layout as if it's already hidden
+        mToast.getView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+
+        // show the confirmation
+        mToast.show();
+    }
+
+    private Runnable confirmAction(final String userPkg) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                mConfirmedUserPackages.add(userPkg);
+                handleHide();
+            }
+        };
+    }
+
+    private final class H extends Handler {
+        private static final int SHOW = 0;
+        private static final int HIDE = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case SHOW:
+                    handleShow((String)msg.obj);
+                    break;
+                case HIDE:
+                    handleHide();
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index bb0d248..a0e6dd1 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -3916,13 +3916,13 @@
             Random rand = new Random();
             mParams = params;
 
-            try {
-                if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
-                    log("isMobileOk: not mobile capable");
-                    result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
-                    return result;
-                }
+            if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
+                log("isMobileOk: not mobile capable");
+                result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+                return result;
+            }
 
+            try {
                 // Enable fail fast as we'll do retries here and use a
                 // hipri connection so the default connection stays active.
                 log("isMobileOk: start hipri url=" + params.mUrl);
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index a32699a..9761441 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -420,19 +420,7 @@
             Slog.e(TAG,  "no geocoder provider found");
         }
 
-        // bind to geofence provider
-        GeofenceProxy provider = GeofenceProxy.createAndBind(mContext,
-                com.android.internal.R.bool.config_enableGeofenceOverlay,
-                com.android.internal.R.string.config_geofenceProviderPackageName,
-                com.android.internal.R.array.config_locationProviderPackageNames,
-                mLocationHandler,
-                gpsProvider.getGpsGeofenceProxy());
-        if (provider == null) {
-            Slog.e(TAG,  "no geofence provider found");
-        }
-
         // bind to fused provider
-        // TODO: [GeofenceIntegration] bind #getGeofenceHardware() with the GeofenceProxy
         FlpHardwareProvider flpHardwareProvider = FlpHardwareProvider.getInstance(mContext);
         FusedProxy fusedProxy = FusedProxy.createAndBind(
                 mContext,
@@ -441,10 +429,21 @@
                 com.android.internal.R.bool.config_enableFusedLocationOverlay,
                 com.android.internal.R.string.config_fusedLocationProviderPackageName,
                 com.android.internal.R.array.config_locationProviderPackageNames);
-
         if(fusedProxy == null) {
             Slog.e(TAG, "No FusedProvider found.");
         }
+
+        // bind to geofence provider
+        GeofenceProxy provider = GeofenceProxy.createAndBind(mContext,
+                com.android.internal.R.bool.config_enableGeofenceOverlay,
+                com.android.internal.R.string.config_geofenceProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
+                mLocationHandler,
+                gpsProvider.getGpsGeofenceProxy(),
+                flpHardwareProvider.getGeofenceHardware());
+        if (provider == null) {
+            Slog.e(TAG,  "no geofence provider found");
+        }
     }
 
     /**
@@ -549,30 +548,52 @@
             return s.toString();
         }
 
+        /**
+         * Update AppOp monitoring for this receiver.
+         *
+         * @param allow If true receiver is currently active, if false it's been removed.
+         */
         public void updateMonitoring(boolean allow) {
             if (mHideFromAppOps) {
                 return;
             }
 
+            boolean requestingLocation = false;
+            boolean requestingHighPowerLocation = false;
+            if (allow) {
+                // See if receiver has any enabled update records.  Also note if any update records
+                // are high power (has a high power provider with an interval under a threshold).
+                for (UpdateRecord updateRecord : mUpdateRecords.values()) {
+                    if (isAllowedByCurrentUserSettingsLocked(updateRecord.mProvider)) {
+                        requestingLocation = true;
+                        LocationProviderInterface locationProvider
+                            = mProvidersByName.get(updateRecord.mProvider);
+                        ProviderProperties properties = locationProvider != null
+                                ? locationProvider.getProperties() : null;
+                        if (properties != null
+                                && properties.mPowerRequirement == Criteria.POWER_HIGH
+                                && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
+                            requestingHighPowerLocation = true;
+                            break;
+                        }
+                    }
+                }
+            }
+
             // First update monitoring of any location request (including high power).
-            mOpMonitoring = updateMonitoring(allow, mOpMonitoring,
+            mOpMonitoring = updateMonitoring(
+                    requestingLocation,
+                    mOpMonitoring,
                     AppOpsManager.OP_MONITOR_LOCATION);
 
             // Now update monitoring of high power requests only.
-            // A high power request is any gps request with interval under a threshold.
-            boolean allowHighPower = allow;
-            if (allowHighPower) {
-                UpdateRecord gpsRecord = mUpdateRecords.get(LocationManager.GPS_PROVIDER);
-                if (gpsRecord == null
-                        || gpsRecord.mRequest.getInterval() > HIGH_POWER_INTERVAL_MS) {
-                    allowHighPower = false;
-                }
-            }
             boolean wasHighPowerMonitoring = mOpHighPowerMonitoring;
-            mOpHighPowerMonitoring = updateMonitoring(allowHighPower, mOpHighPowerMonitoring,
+            mOpHighPowerMonitoring = updateMonitoring(
+                    requestingHighPowerLocation,
+                    mOpHighPowerMonitoring,
                     AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION);
             if (mOpHighPowerMonitoring != wasHighPowerMonitoring) {
-                // send an intent to notify that a high power request has been added/removed.
+                // Send an intent to notify that a high power request has been added/removed.
                 Intent intent = new Intent(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
                 mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
             }
@@ -689,6 +710,10 @@
         }
 
         public boolean callProviderEnabledLocked(String provider, boolean enabled) {
+            // First update AppOp monitoring.
+            // An app may get/lose location access as providers are enabled/disabled.
+            updateMonitoring(true);
+
             if (mListener != null) {
                 try {
                     synchronized (this) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index a6d7e3c9..be6119d 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -8062,12 +8062,10 @@
             if (activity == null) {
                 Slog.w(TAG, "getAssistContextExtras error: no resumed activity");
                 validActivity = false;
-            }
-            if (activity.app == null || activity.app.thread == null) {
+            } else if (activity.app == null || activity.app.thread == null) {
                 Slog.w(TAG, "getAssistContextExtras error: no process for " + activity);
                 validActivity = false;
-            }
-            if (activity.app.pid == Binder.getCallingPid()) {
+            } else if (activity.app.pid == Binder.getCallingPid()) {
                 Slog.w(TAG, "getAssistContextExtras error: request process same as " + activity);
                 validActivity = false;
             }
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 8634929..0808861 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1684,9 +1684,6 @@
                         setLaunchHomeTaskNextFlag(sourceRecord, null, targetStack);
                         targetStack.resumeTopActivityLocked(null);
                     }
-                    if (r.task == null)  Slog.v(TAG,
-                        "startActivityUncheckedLocked: task left null",
-                        new RuntimeException("here").fillInStackTrace());
                     return ActivityManager.START_DELIVERED_TO_TOP;
                 }
             }
diff --git a/services/java/com/android/server/dreams/DreamManagerService.java b/services/java/com/android/server/dreams/DreamManagerService.java
index 21e54fe..b6e7781 100644
--- a/services/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/java/com/android/server/dreams/DreamManagerService.java
@@ -86,7 +86,13 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+        if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump DreamManager from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
 
         pw.println("DREAM MANAGER (dumpsys dreams)");
         pw.println();
diff --git a/services/java/com/android/server/location/FlpHardwareProvider.java b/services/java/com/android/server/location/FlpHardwareProvider.java
index 226c18c..ebeccfb 100644
--- a/services/java/com/android/server/location/FlpHardwareProvider.java
+++ b/services/java/com/android/server/location/FlpHardwareProvider.java
@@ -16,12 +16,13 @@
 
 package com.android.server.location;
 
+import android.hardware.location.GeofenceHardware;
 import android.hardware.location.GeofenceHardwareImpl;
+import android.hardware.location.GeofenceHardwareRequestParcelable;
 import android.hardware.location.IFusedLocationHardware;
 import android.hardware.location.IFusedLocationHardwareSink;
 import android.location.IFusedGeofenceHardware;
 import android.location.FusedBatchOptions;
-import android.location.Geofence;
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
@@ -49,6 +50,15 @@
     private final Context mContext;
     private final Object mLocationSinkLock = new Object();
 
+    // FlpHal result codes, they must be equal to the ones in fused_location.h
+    private static final int FLP_RESULT_SUCCESS = 0;
+    private static final int FLP_RESULT_ERROR = -1;
+    private static final int FLP_RESULT_INSUFFICIENT_MEMORY = -2;
+    private static final int FLP_RESULT_TOO_MANY_GEOFENCES = -3;
+    private static final int FLP_RESULT_ID_EXISTS = -4;
+    private static final int FLP_RESULT_ID_UNKNOWN = -5;
+    private static final int FLP_RESULT_INVALID_GEOFENCE_TRANSITION = -6;
+
     public static FlpHardwareProvider getInstance(Context context) {
         if (sSingletonInstance == null) {
             sSingletonInstance = new FlpHardwareProvider(context);
@@ -120,29 +130,46 @@
             Location location,
             int transition,
             long timestamp,
-            int sourcesUsed
-            ) {
-        // TODO: [GeofenceIntegration] change GeofenceHardwareImpl to accept a location object
+            int sourcesUsed) {
+        getGeofenceHardwareSink().reportGeofenceTransition(
+                geofenceId,
+                updateLocationInformation(location),
+                transition,
+                timestamp,
+                GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
+                sourcesUsed);
     }
 
     private void onGeofenceMonitorStatus(int status, int source, Location location) {
-        // TODO: [GeofenceIntegration]
+        getGeofenceHardwareSink().reportGeofenceMonitorStatus(
+                GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
+                status,
+                updateLocationInformation(location),
+                source);
     }
 
     private void onGeofenceAdd(int geofenceId, int result) {
-        // TODO: [GeofenceIntegration] map between GPS and FLP results to pass a consistent status
+        getGeofenceHardwareSink().reportGeofenceAddStatus(
+                geofenceId,
+                translateToGeofenceHardwareStatus(result));
     }
 
     private void onGeofenceRemove(int geofenceId, int result) {
-        // TODO: [GeofenceIntegration] map between GPS and FLP results to pass a consistent status
+        getGeofenceHardwareSink().reportGeofenceRemoveStatus(
+                geofenceId,
+                translateToGeofenceHardwareStatus(result));
     }
 
     private void onGeofencePause(int geofenceId, int result) {
-        // TODO; [GeofenceIntegration] map between GPS and FLP results
+        getGeofenceHardwareSink().reportGeofencePauseStatus(
+                geofenceId,
+                translateToGeofenceHardwareStatus(result));
     }
 
     private void onGeofenceResume(int geofenceId, int result) {
-        // TODO: [GeofenceIntegration] map between GPS and FLP results
+        getGeofenceHardwareSink().reportGeofenceResumeStatus(
+                geofenceId,
+                translateToGeofenceHardwareStatus(result));
     }
 
     /**
@@ -175,7 +202,8 @@
 
     // FlpGeofencingInterface members
     private native boolean nativeIsGeofencingSupported();
-    private native void nativeAddGeofences(int[] geofenceIdsArray, Geofence[] geofencesArray);
+    private native void nativeAddGeofences(
+            GeofenceHardwareRequestParcelable[] geofenceRequestsArray);
     private native void nativePauseGeofence(int geofenceId);
     private native void  nativeResumeGeofence(int geofenceId, int monitorTransitions);
     private native void nativeModifyGeofenceOption(
@@ -281,8 +309,8 @@
         }
 
         @Override
-        public void addGeofences(int[] geofenceIdsArray, Geofence[] geofencesArray) {
-            nativeAddGeofences(geofenceIdsArray, geofencesArray);
+        public void addGeofences(GeofenceHardwareRequestParcelable[] geofenceRequestsArray) {
+            nativeAddGeofences(geofenceRequestsArray);
         }
 
         @Override
@@ -305,17 +333,15 @@
                 int lastTransition,
                 int monitorTransitions,
                 int notificationResponsiveness,
-                int unknownTimer
-                ) {
-            // TODO: [GeofenceIntegration] set sourcesToUse to the right value
-            // TODO: expose sourcesToUse externally when needed
+                int unknownTimer,
+                int sourcesToUse) {
             nativeModifyGeofenceOption(
                     geofenceId,
                     lastTransition,
                     monitorTransitions,
                     notificationResponsiveness,
                     unknownTimer,
-                    /* sourcesToUse */ 0xFFFF);
+                    sourcesToUse);
         }
     };
 
@@ -347,10 +373,39 @@
 
     private GeofenceHardwareImpl getGeofenceHardwareSink() {
         if (mGeofenceHardwareSink == null) {
-            // TODO: [GeofenceIntegration] we need to register ourselves with GeofenceHardwareImpl
             mGeofenceHardwareSink = GeofenceHardwareImpl.getInstance(mContext);
         }
 
         return mGeofenceHardwareSink;
     }
-}
\ No newline at end of file
+
+    private static int translateToGeofenceHardwareStatus(int flpHalResult) {
+        switch(flpHalResult) {
+            case FLP_RESULT_SUCCESS:
+                return GeofenceHardware.GEOFENCE_SUCCESS;
+            case FLP_RESULT_ERROR:
+                return GeofenceHardware.GEOFENCE_FAILURE;
+            // TODO: uncomment this once the ERROR definition is marked public
+            //case FLP_RESULT_INSUFFICIENT_MEMORY:
+            //    return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY;
+            case FLP_RESULT_TOO_MANY_GEOFENCES:
+                return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
+            case FLP_RESULT_ID_EXISTS:
+                return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS;
+            case FLP_RESULT_ID_UNKNOWN:
+                return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN;
+            case FLP_RESULT_INVALID_GEOFENCE_TRANSITION:
+                return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION;
+            default:
+                Log.e(TAG, String.format("Invalid FlpHal result code: %d", flpHalResult));
+                return GeofenceHardware.GEOFENCE_FAILURE;
+        }
+    }
+
+    private Location updateLocationInformation(Location location) {
+        location.setProvider(LocationManager.FUSED_PROVIDER);
+        // set the elapsed time-stamp just as GPS provider does
+        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        return location;
+    }
+}
diff --git a/services/java/com/android/server/location/GeofenceProxy.java b/services/java/com/android/server/location/GeofenceProxy.java
index f6be27b..a86c923 100644
--- a/services/java/com/android/server/location/GeofenceProxy.java
+++ b/services/java/com/android/server/location/GeofenceProxy.java
@@ -22,6 +22,7 @@
 import android.hardware.location.IGeofenceHardware;
 import android.location.IGeofenceProvider;
 import android.location.IGpsGeofenceHardware;
+import android.location.IFusedGeofenceHardware;
 import android.content.Context;
 import android.os.Handler;
 import android.os.IBinder;
@@ -44,6 +45,7 @@
     private Context mContext;
     private IGeofenceHardware mGeofenceHardware;
     private IGpsGeofenceHardware mGpsGeofenceHardware;
+    private IFusedGeofenceHardware mFusedGeofenceHardware;
 
     private static final int GEOFENCE_PROVIDER_CONNECTED = 1;
     private static final int GEOFENCE_HARDWARE_CONNECTED = 2;
@@ -60,9 +62,11 @@
 
     public static GeofenceProxy createAndBind(Context context,
             int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence) {
+            int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence,
+            IFusedGeofenceHardware fusedGeofenceHardware) {
         GeofenceProxy proxy = new GeofenceProxy(context, overlaySwitchResId,
-            defaultServicePackageNameResId, initialPackageNamesResId, handler, gpsGeofence);
+            defaultServicePackageNameResId, initialPackageNamesResId, handler, gpsGeofence,
+            fusedGeofenceHardware);
         if (proxy.bindGeofenceProvider()) {
             return proxy;
         } else {
@@ -72,11 +76,13 @@
 
     private GeofenceProxy(Context context,
             int overlaySwitchResId, int defaultServicePackageNameResId,
-            int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence) {
+            int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence,
+            IFusedGeofenceHardware fusedGeofenceHardware) {
         mContext = context;
         mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId,
             defaultServicePackageNameResId, initialPackageNamesResId, mRunnable, handler);
         mGpsGeofenceHardware = gpsGeofence;
+        mFusedGeofenceHardware = fusedGeofenceHardware;
         bindHardwareGeofence();
     }
 
@@ -123,6 +129,13 @@
         }
     }
 
+    private void setFusedGeofence() {
+        try {
+            mGeofenceHardware.setFusedGeofenceHardware(mFusedGeofenceHardware);
+        } catch(RemoteException e) {
+            Log.e(TAG, "Error while connecting to GeofenceHardwareService");
+        }
+    }
 
     // This needs to be reworked, when more services get added,
     // Might need a state machine or add a framework utility class,
@@ -142,6 +155,7 @@
                     break;
                 case GEOFENCE_HARDWARE_CONNECTED:
                     setGpsGeofence();
+                    setFusedGeofence();
                     mGeofenceHardwareConnected = true;
                     if (mGeofenceProviderConnected) {
                         setGeofenceHardwareInProvider();
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
index 38453c8..6053c61 100644
--- a/services/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -24,9 +24,10 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.Cursor;
+import android.hardware.location.GeofenceHardware;
 import android.hardware.location.GeofenceHardwareImpl;
-import android.hardware.location.IGeofenceHardware;
 import android.location.Criteria;
+import android.location.FusedBatchOptions;
 import android.location.IGpsGeofenceHardware;
 import android.location.IGpsStatusListener;
 import android.location.IGpsStatusProvider;
@@ -195,6 +196,17 @@
 
     private static final String PROPERTIES_FILE = "/etc/gps.conf";
 
+    private static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L;
+    private static final int GPS_GEOFENCE_AVAILABLE = 1<<1L;
+
+    // GPS Geofence errors. Should match gps.h constants.
+    private static final int GPS_GEOFENCE_OPERATION_SUCCESS = 0;
+    private static final int GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 100;
+    private static final int GPS_GEOFENCE_ERROR_ID_EXISTS  = -101;
+    private static final int GPS_GEOFENCE_ERROR_ID_UNKNOWN = -102;
+    private static final int GPS_GEOFENCE_ERROR_INVALID_TRANSITION = -103;
+    private static final int GPS_GEOFENCE_ERROR_GENERIC = -149;
+
     /** simpler wrapper for ProviderRequest + Worksource */
     private static class GpsRequest {
         public ProviderRequest request;
@@ -501,7 +513,7 @@
                 LocationManager locManager =
                         (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
                 locManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
-                        0, 0, new NetworkLocationListener(), mHandler.getLooper());                
+                        0, 0, new NetworkLocationListener(), mHandler.getLooper());
             }
         });
     }
@@ -1405,6 +1417,62 @@
     }
 
     /**
+     * Helper method to construct a location object.
+     */
+    private Location buildLocation(
+            int flags,
+            double latitude,
+            double longitude,
+            double altitude,
+            float speed,
+            float bearing,
+            float accuracy,
+            long timestamp) {
+        Location location = new Location(LocationManager.GPS_PROVIDER);
+        if((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
+            location.setLatitude(latitude);
+            location.setLongitude(longitude);
+            location.setTime(timestamp);
+            location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        }
+        if((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
+            location.setAltitude(altitude);
+        }
+        if((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
+            location.setSpeed(speed);
+        }
+        if((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
+            location.setBearing(bearing);
+        }
+        if((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
+            location.setAccuracy(accuracy);
+        }
+        return location;
+    }
+
+    /**
+     * Converts the GPS HAL status to the internal Geofence Hardware status.
+     */
+    private int getGeofenceStatus(int status) {
+        switch(status) {
+            case GPS_GEOFENCE_OPERATION_SUCCESS:
+                return GeofenceHardware.GEOFENCE_SUCCESS;
+            case GPS_GEOFENCE_ERROR_GENERIC:
+                return GeofenceHardware.GEOFENCE_FAILURE;
+            case GPS_GEOFENCE_ERROR_ID_EXISTS:
+                return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS;
+            case GPS_GEOFENCE_ERROR_INVALID_TRANSITION:
+                return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION;
+            case GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES:
+                return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
+            case GPS_GEOFENCE_ERROR_ID_UNKNOWN:
+                return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN;
+            default:
+                return -1;
+        }
+    }
+
+    /**
      * Called from native to report GPS Geofence transition
      * All geofence callbacks are called on the same thread
      */
@@ -1414,8 +1482,22 @@
         if (mGeofenceHardwareImpl == null) {
             mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
         }
-        mGeofenceHardwareImpl.reportGpsGeofenceTransition(geofenceId, flags, latitude, longitude,
-           altitude, speed, bearing, accuracy, timestamp, transition, transitionTimestamp);
+        Location location = buildLocation(
+                flags,
+                latitude,
+                longitude,
+                altitude,
+                speed,
+                bearing,
+                accuracy,
+                timestamp);
+        mGeofenceHardwareImpl.reportGeofenceTransition(
+                geofenceId,
+                location,
+                transition,
+                transitionTimestamp,
+                GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
+                FusedBatchOptions.SourceTechnologies.GNSS);
     }
 
     /**
@@ -1427,8 +1509,24 @@
         if (mGeofenceHardwareImpl == null) {
             mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
         }
-        mGeofenceHardwareImpl.reportGpsGeofenceStatus(status, flags, latitude, longitude, altitude,
-            speed, bearing, accuracy, timestamp);
+        Location location = buildLocation(
+                flags,
+                latitude,
+                longitude,
+                altitude,
+                speed,
+                bearing,
+                accuracy,
+                timestamp);
+        int monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
+        if(status == GPS_GEOFENCE_AVAILABLE) {
+            monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE;
+        }
+        mGeofenceHardwareImpl.reportGeofenceMonitorStatus(
+                GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
+                monitorStatus,
+                location,
+                FusedBatchOptions.SourceTechnologies.GNSS);
     }
 
     /**
@@ -1438,7 +1536,7 @@
         if (mGeofenceHardwareImpl == null) {
             mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
         }
-        mGeofenceHardwareImpl.reportGpsGeofenceAddStatus(geofenceId, status);
+        mGeofenceHardwareImpl.reportGeofenceAddStatus(geofenceId, getGeofenceStatus(status));
     }
 
     /**
@@ -1448,7 +1546,7 @@
         if (mGeofenceHardwareImpl == null) {
             mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
         }
-        mGeofenceHardwareImpl.reportGpsGeofenceRemoveStatus(geofenceId, status);
+        mGeofenceHardwareImpl.reportGeofenceRemoveStatus(geofenceId, getGeofenceStatus(status));
     }
 
     /**
@@ -1458,7 +1556,7 @@
         if (mGeofenceHardwareImpl == null) {
             mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
         }
-        mGeofenceHardwareImpl.reportGpsGeofencePauseStatus(geofenceId, status);
+        mGeofenceHardwareImpl.reportGeofencePauseStatus(geofenceId, getGeofenceStatus(status));
     }
 
     /**
@@ -1468,7 +1566,7 @@
         if (mGeofenceHardwareImpl == null) {
             mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
         }
-        mGeofenceHardwareImpl.reportGpsGeofenceResumeStatus(geofenceId, status);
+        mGeofenceHardwareImpl.reportGeofenceResumeStatus(geofenceId, getGeofenceStatus(status));
     }
 
     //=============================================================
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 322de6c..491dddc 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -110,6 +110,7 @@
     }
 
     private void handleBinderDied() {
+        mPendingCommands.clear();
         ensureUnbound();
     }
 
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index 6e0e055..db030f1 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -25,18 +25,20 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
-import android.net.wifi.IWifiManager;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateMachine;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiWatchdogStateMachine;
 import android.net.DhcpInfo;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiStateMachine;
+import android.net.wifi.WifiWatchdogStateMachine;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Messenger;
@@ -63,6 +65,7 @@
 import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -121,6 +124,8 @@
     /* Tracks the persisted states for wi-fi & airplane mode */
     final WifiSettingsStore mSettingsStore;
 
+    final boolean mBatchedScanSupported;
+
     /**
      * Asynchronous channel to WifiStateMachine
      */
@@ -246,6 +251,9 @@
         mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
         mWifiController.start();
 
+        mBatchedScanSupported = mContext.getResources().getBoolean(
+                R.bool.config_wifi_batched_scan_supported);
+
         registerForScanModeChange();
         mContext.registerReceiver(
                 new BroadcastReceiver() {
@@ -314,6 +322,142 @@
         mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);
     }
 
+    private class BatchedScanRequest extends DeathRecipient {
+        BatchedScanSettings settings;
+        int uid;
+
+        BatchedScanRequest(BatchedScanSettings settings, IBinder binder, int uid) {
+            super(0, null, binder, null);
+            this.settings = settings;
+            this.uid = uid;
+        }
+        public void binderDied() {
+            stopBatchedScan(settings, mBinder);
+        }
+        public String toString() {
+            return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}";
+        }
+    }
+
+    private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>();
+
+    public boolean isBatchedScanSupported() {
+        return mBatchedScanSupported;
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#requestBatchedScan()}
+     */
+    public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) {
+        enforceChangePermission();
+        if (mBatchedScanSupported == false) return false;
+        requested = new BatchedScanSettings(requested);
+        if (requested.isInvalid()) return false;
+        BatchedScanRequest r = new BatchedScanRequest(requested, binder, Binder.getCallingUid());
+        synchronized(mBatchedScanners) {
+            mBatchedScanners.add(r);
+            resolveBatchedScannersLocked();
+        }
+        return true;
+    }
+
+    public List<BatchedScanResult> getBatchedScanResults(String callingPackage) {
+        enforceAccessPermission();
+        if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>();
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return new ArrayList<BatchedScanResult>();
+            }
+            int currentUser = ActivityManager.getCurrentUser();
+            if (userId != currentUser) {
+                return new ArrayList<BatchedScanResult>();
+            } else {
+                return mWifiStateMachine.syncGetBatchedScanResultsList();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+
+    public void stopBatchedScan(BatchedScanSettings settings, IBinder binder) {
+        enforceChangePermission();
+        if (mBatchedScanSupported == false) return;
+        synchronized(mBatchedScanners) {
+            BatchedScanRequest found = null;
+            for (BatchedScanRequest r : mBatchedScanners) {
+                if (r.mBinder.equals(binder) && r.settings.equals(settings)) {
+                    found = r;
+                    break;
+                }
+            }
+            if (found != null) {
+                mBatchedScanners.remove(found);
+                resolveBatchedScannersLocked();
+            }
+        }
+    }
+
+    private void resolveBatchedScannersLocked() {
+        BatchedScanSettings setting = new BatchedScanSettings();
+        setting.scanIntervalSec = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+        int responsibleUid = 0;
+        setting.channelSet = new ArrayList<String>();
+
+        if (mBatchedScanners.size() == 0) {
+            mWifiStateMachine.setBatchedScanSettings(null, 0);
+            return;
+        }
+
+        for (BatchedScanRequest r : mBatchedScanners) {
+            BatchedScanSettings s = r.settings;
+            if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
+                    s.maxScansPerBatch < setting.maxScansPerBatch) {
+                setting.maxScansPerBatch = s.maxScansPerBatch;
+                responsibleUid = r.uid;
+            }
+            if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
+                    s.maxApPerScan > setting.maxApPerScan) {
+                setting.maxApPerScan = s.maxApPerScan;
+            }
+            if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
+                    s.scanIntervalSec < setting.scanIntervalSec) {
+                setting.scanIntervalSec = s.scanIntervalSec;
+                responsibleUid = r.uid;
+            }
+            if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
+                    s.maxApForDistance > setting.maxApForDistance) {
+                setting.maxApForDistance = s.maxApForDistance;
+            }
+            if (s.channelSet != null) {
+                for (String i : s.channelSet) {
+                    if (setting.channelSet.contains(i) == false) setting.channelSet.add(i);
+                }
+            }
+        }
+        if (setting.channelSet.size() == 0) setting.channelSet = null;
+        if (setting.scanIntervalSec < BatchedScanSettings.MIN_INTERVAL_SEC) {
+            setting.scanIntervalSec = BatchedScanSettings.MIN_INTERVAL_SEC;
+        }
+        if (setting.maxScansPerBatch == BatchedScanSettings.UNSPECIFIED) {
+            setting.maxScansPerBatch = BatchedScanSettings.DEFAULT_SCANS_PER_BATCH;
+        }
+        if (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED) {
+            setting.maxApPerScan = BatchedScanSettings.DEFAULT_AP_PER_SCAN;
+        }
+        if (setting.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
+            setting.scanIntervalSec = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+        }
+        if (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED) {
+            setting.maxApForDistance = BatchedScanSettings.DEFAULT_AP_FOR_DISTANCE;
+        }
+        mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid);
+    }
+
     private void enforceAccessPermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
                                                 "WifiService");
@@ -569,11 +713,11 @@
         int userId = UserHandle.getCallingUserId();
         int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
-        if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
-                != AppOpsManager.MODE_ALLOWED) {
-            return new ArrayList<ScanResult>();
-        }
         try {
+            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return new ArrayList<ScanResult>();
+            }
             int currentUser = ActivityManager.getCurrentUser();
             if (userId != currentUser) {
                 return new ArrayList<ScanResult>();
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 07e1e12..8de2fb0 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -10746,7 +10746,7 @@
         if (display == null) {
             throw new IllegalArgumentException("getDisplayContent: display must not be null");
         }
-        newDisplayContentLocked(display);
+        getDisplayContentLocked(display.getDisplayId());
     }
 
     /**
diff --git a/services/jni/com_android_server_location_FlpHardwareProvider.cpp b/services/jni/com_android_server_location_FlpHardwareProvider.cpp
index 48b86db..c871828 100644
--- a/services/jni/com_android_server_location_FlpHardwareProvider.cpp
+++ b/services/jni/com_android_server_location_FlpHardwareProvider.cpp
@@ -261,6 +261,75 @@
 }
 
 /*
+ * Helper function to unwrap Geofence structures from the Java Runtime calls.
+ */
+static void TranslateGeofenceFromGeofenceHardwareRequestParcelable(
+    JNIEnv* env,
+    jobject geofenceRequestObject,
+    Geofence& geofence) {
+  jclass geofenceRequestClass = env->GetObjectClass(geofenceRequestObject);
+
+  jmethodID getId = env->GetMethodID(geofenceRequestClass, "getId", "()I");
+  geofence.geofence_id = env->CallIntMethod(geofenceRequestObject, getId);
+
+  jmethodID getType = env->GetMethodID(geofenceRequestClass, "getType", "()I");
+  // this works because GeofenceHardwareRequest.java and fused_location.h have
+  // the same notion of geofence types
+  GeofenceType type = (GeofenceType)env->CallIntMethod(geofenceRequestObject, getType);
+  if(type != TYPE_CIRCLE) {
+    ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
+  }
+  geofence.data->type = type;
+  GeofenceCircle& circle = geofence.data->geofence.circle;
+
+  jmethodID getLatitude = env->GetMethodID(
+      geofenceRequestClass,
+      "getLatitude",
+      "()D");
+  circle.latitude = env->CallDoubleMethod(geofenceRequestObject, getLatitude);
+
+  jmethodID getLongitude = env->GetMethodID(
+      geofenceRequestClass,
+      "getLongitude",
+      "()D");
+  circle.longitude = env->CallDoubleMethod(geofenceRequestObject, getLongitude);
+
+  jmethodID getRadius = env->GetMethodID(geofenceRequestClass, "getRadius", "()D");
+  circle.radius_m = env->CallDoubleMethod(geofenceRequestObject, getRadius);
+
+  GeofenceOptions* options = geofence.options;
+  jmethodID getMonitorTransitions = env->GetMethodID(
+      geofenceRequestClass,
+      "getMonitorTransitions",
+      "()I");
+  options->monitor_transitions = env->CallIntMethod(
+      geofenceRequestObject,
+      getMonitorTransitions);
+
+  jmethodID getUnknownTimer = env->GetMethodID(
+      geofenceRequestClass,
+      "getUnknownTimer",
+      "()I");
+  options->unknown_timer_ms = env->CallIntMethod(geofenceRequestObject, getUnknownTimer);
+
+  jmethodID getNotificationResponsiveness = env->GetMethodID(
+      geofenceRequestClass,
+      "getNotificationResponsiveness",
+      "()D");
+  options->notification_responsivenes_ms = env->CallIntMethod(
+      geofenceRequestObject,
+      getNotificationResponsiveness);
+
+  jmethodID getLastTransition = env->GetMethodID(
+      geofenceRequestClass,
+      "getLastTransition",
+      "()I");
+  options->last_transition = env->CallIntMethod(geofenceRequestObject, getLastTransition);
+
+  // TODO: set data.sources_to_use when available
+}
+
+/*
  * Helper function to transform FlpLocation into a java object.
  */
 static void TranslateToObject(const FlpLocation* location, jobject& locationObject) {
@@ -559,7 +628,7 @@
   }
 
   err = module->methods->open(
-        module, 
+        module,
         FUSED_LOCATION_HARDWARE_MODULE_ID, &sHardwareDevice);
   if(err != 0) {
     ALOGE("Error opening device '%s': %d", FUSED_LOCATION_HARDWARE_MODULE_ID, err);
@@ -749,10 +818,9 @@
 static void AddGeofences(
     JNIEnv* env,
     jobject object,
-    jintArray geofenceIdsArray,
-    jobjectArray geofencesArray) {
-  if(geofencesArray == NULL) {
-    ALOGE("Invalid Geofences to add: %p", geofencesArray);
+    jobjectArray geofenceRequestsArray) {
+  if(geofenceRequestsArray == NULL) {
+    ALOGE("Invalid Geofences to add: %p", geofenceRequestsArray);
     ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
   }
 
@@ -760,23 +828,32 @@
     ThrowOnError(env, FLP_RESULT_ERROR, __FUNCTION__);
   }
 
-  jint geofencesCount = env->GetArrayLength(geofenceIdsArray);
-  Geofence* geofences = new Geofence[geofencesCount];
+  jint geofenceRequestsCount = env->GetArrayLength(geofenceRequestsArray);
+  if(geofenceRequestsCount == 0) {
+    return;
+  }
+
+  Geofence* geofences = new Geofence[geofenceRequestsCount];
   if (geofences == NULL) {
     ThrowOnError(env, FLP_RESULT_INSUFFICIENT_MEMORY, __FUNCTION__);
   }
 
-  jint* ids = env->GetIntArrayElements(geofenceIdsArray, /* isCopy */ NULL);
-  for (int i = 0; i < geofencesCount; ++i) {
-    geofences[i].geofence_id = ids[i];
+  for (int i = 0; i < geofenceRequestsCount; ++i) {
+    geofences[i].data = new GeofenceData();
+    geofences[i].options = new GeofenceOptions();
+    jobject geofenceObject = env->GetObjectArrayElement(geofenceRequestsArray, i);
 
-    // TODO: fill in the GeofenceData
-
-    // TODO: fill in the GeofenceOptions
+    TranslateGeofenceFromGeofenceHardwareRequestParcelable(env, geofenceObject, geofences[i]);
   }
 
-  sFlpGeofencingInterface->add_geofences(geofencesCount, &geofences);
-  if (geofences != NULL) delete[] geofences;
+  sFlpGeofencingInterface->add_geofences(geofenceRequestsCount, &geofences);
+  if (geofences != NULL) {
+    for(int i = 0; i < geofenceRequestsCount; ++i) {
+      delete geofences[i].data;
+      delete geofences[i].options;
+    }
+    delete[] geofences;
+  }
 }
 
 static void PauseGeofence(JNIEnv* env, jobject object, jint geofenceId) {
@@ -847,41 +924,41 @@
   {"nativeCleanup", "()V", reinterpret_cast<void*>(Cleanup)},
   {"nativeIsSupported", "()Z", reinterpret_cast<void*>(IsSupported)},
   {"nativeGetBatchSize", "()I", reinterpret_cast<void*>(GetBatchSize)},
-  {"nativeStartBatching", 
-        "(ILandroid/location/FusedBatchOptions;)V", 
+  {"nativeStartBatching",
+        "(ILandroid/location/FusedBatchOptions;)V",
         reinterpret_cast<void*>(StartBatching)},
-  {"nativeUpdateBatchingOptions", 
-        "(ILandroid/location/FusedBatchOptions;)V", 
+  {"nativeUpdateBatchingOptions",
+        "(ILandroid/location/FusedBatchOptions;)V",
         reinterpret_cast<void*>(UpdateBatchingOptions)},
   {"nativeStopBatching", "(I)V", reinterpret_cast<void*>(StopBatching)},
-  {"nativeRequestBatchedLocation", 
-        "(I)V", 
+  {"nativeRequestBatchedLocation",
+        "(I)V",
         reinterpret_cast<void*>(GetBatchedLocation)},
-  {"nativeInjectLocation", 
-        "(Landroid/location/Location;)V", 
+  {"nativeInjectLocation",
+        "(Landroid/location/Location;)V",
         reinterpret_cast<void*>(InjectLocation)},
-  {"nativeIsDiagnosticSupported", 
-        "()Z", 
+  {"nativeIsDiagnosticSupported",
+        "()Z",
         reinterpret_cast<void*>(IsDiagnosticSupported)},
-  {"nativeInjectDiagnosticData", 
-        "(Ljava/lang/String;)V", 
+  {"nativeInjectDiagnosticData",
+        "(Ljava/lang/String;)V",
         reinterpret_cast<void*>(InjectDiagnosticData)},
-  {"nativeIsDeviceContextSupported", 
-        "()Z", 
+  {"nativeIsDeviceContextSupported",
+        "()Z",
         reinterpret_cast<void*>(IsDeviceContextSupported)},
-  {"nativeInjectDeviceContext", 
-        "(I)V", 
+  {"nativeInjectDeviceContext",
+        "(I)V",
         reinterpret_cast<void*>(InjectDeviceContext)},
-  {"nativeIsGeofencingSupported", 
-        "()Z", 
+  {"nativeIsGeofencingSupported",
+        "()Z",
         reinterpret_cast<void*>(IsGeofencingSupported)},
-  {"nativeAddGeofences", 
-        "([I[Landroid/location/Geofence;)V", 
+  {"nativeAddGeofences",
+        "([Landroid/hardware/location/GeofenceHardwareRequestParcelable;)V",
         reinterpret_cast<void*>(AddGeofences)},
   {"nativePauseGeofence", "(I)V", reinterpret_cast<void*>(PauseGeofence)},
   {"nativeResumeGeofence", "(II)V", reinterpret_cast<void*>(ResumeGeofence)},
-  {"nativeModifyGeofenceOption", 
-        "(IIIIII)V", 
+  {"nativeModifyGeofenceOption",
+        "(IIIIII)V",
         reinterpret_cast<void*>(ModifyGeofenceOption)},
   {"nativeRemoveGeofences", "([I)V", reinterpret_cast<void*>(RemoveGeofences)}
 };
diff --git a/wifi/java/android/net/wifi/BatchedScanResult.aidl b/wifi/java/android/net/wifi/BatchedScanResult.aidl
new file mode 100644
index 0000000..a70bc0a
--- /dev/null
+++ b/wifi/java/android/net/wifi/BatchedScanResult.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+parcelable BatchedScanResult;
diff --git a/wifi/java/android/net/wifi/BatchedScanResult.java b/wifi/java/android/net/wifi/BatchedScanResult.java
new file mode 100644
index 0000000..eb4e0276
--- /dev/null
+++ b/wifi/java/android/net/wifi/BatchedScanResult.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Describes the Results of a batched set of wifi scans where the firmware performs many
+ * scans and stores the timestamped results without waking the main processor each time.
+ * @hide pending review
+ */
+public class BatchedScanResult implements Parcelable {
+    private static final String TAG = "BatchedScanResult";
+
+    /** Inidcates this scan was interrupted and may only have partial results. */
+    public boolean truncated;
+
+    /** The result of this particular scan. */
+    public final List<ScanResult> scanResults = new ArrayList<ScanResult>();
+
+
+    public BatchedScanResult() {
+    }
+
+    public BatchedScanResult(BatchedScanResult source) {
+        truncated = source.truncated;
+        for (ScanResult s : source.scanResults) scanResults.add(new ScanResult(s));
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("BatchedScanResult: ").
+                append("truncated: ").append(String.valueOf(truncated)).
+                append("scanResults: [");
+        for (ScanResult s : scanResults) {
+            sb.append(" <").append(s.toString()).append("> ");
+        }
+        sb.append(" ]");
+        return sb.toString();
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(truncated ? 1 : 0);
+        dest.writeInt(scanResults.size());
+        for (ScanResult s : scanResults) {
+            s.writeToParcel(dest, flags);
+        }
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<BatchedScanResult> CREATOR =
+        new Creator<BatchedScanResult>() {
+            public BatchedScanResult createFromParcel(Parcel in) {
+                BatchedScanResult result = new BatchedScanResult();
+                result.truncated = (in.readInt() == 1);
+                int count = in.readInt();
+                while (count-- > 0) {
+                    result.scanResults.add(ScanResult.CREATOR.createFromParcel(in));
+                }
+                return result;
+            }
+
+            public BatchedScanResult[] newArray(int size) {
+                return new BatchedScanResult[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/BatchedScanSettings.aidl b/wifi/java/android/net/wifi/BatchedScanSettings.aidl
new file mode 100644
index 0000000..8cfc508
--- /dev/null
+++ b/wifi/java/android/net/wifi/BatchedScanSettings.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); 
+ * you may not use this file except in compliance with the License. 
+ * You may obtain a copy of the License at 
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0 
+ *
+ * Unless required by applicable law or agreed to in writing, software 
+ * distributed under the License is distributed on an "AS IS" BASIS, 
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+parcelable BatchedScanSettings;
diff --git a/wifi/java/android/net/wifi/BatchedScanSettings.java b/wifi/java/android/net/wifi/BatchedScanSettings.java
new file mode 100644
index 0000000..82945d6
--- /dev/null
+++ b/wifi/java/android/net/wifi/BatchedScanSettings.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Describes the settings for batched wifi scans where the firmware performs many
+ * scans and stores the timestamped results without waking the main processor each time.
+ * This can give information over time with minimal battery impact.
+ * @hide pending review
+ */
+public class BatchedScanSettings implements Parcelable {
+    private static final String TAG = "BatchedScanSettings";
+
+    /** Used to indicate no preference for an int value */
+    public final static int UNSPECIFIED = Integer.MAX_VALUE;
+
+    // TODO - make MIN/mAX dynamic and gservices adjustable.
+    public final static int MIN_SCANS_PER_BATCH = 2;
+    public final static int MAX_SCANS_PER_BATCH = 255;
+    public final static int DEFAULT_SCANS_PER_BATCH = MAX_SCANS_PER_BATCH;
+
+    public final static int MIN_AP_PER_SCAN = 2;
+    public final static int MAX_AP_PER_SCAN = 255;
+    public final static int DEFAULT_AP_PER_SCAN = 16;
+
+    public final static int MIN_INTERVAL_SEC = 0;
+    public final static int MAX_INTERVAL_SEC = 3600;
+    public final static int DEFAULT_INTERVAL_SEC = 30;
+
+    public final static int MIN_AP_FOR_DISTANCE = 0;
+    public final static int MAX_AP_FOR_DISTANCE = MAX_AP_PER_SCAN;
+    public final static int DEFAULT_AP_FOR_DISTANCE = 0;
+
+
+    /** The expected number of scans per batch.  Note that the firmware may drop scans
+     *  leading to fewer scans during the normal batch scan duration.  This value need not
+     *  be specified (may be set to {@link UNSPECIFIED}) by the application and we will try
+     *  to scan as many times as the firmware can support.  If another app requests fewer
+     *  scans per batch we will attempt to honor that.
+     */
+    public int maxScansPerBatch;
+
+    /** The maximum desired AP listed per scan.  Fewer AP may be returned if that's all
+     *  that the driver detected.  If another application requests more AP per scan that
+     *  will take precedence.  The if more channels are detected than we request, the APs
+     *  with the lowest signal strength will be dropped.
+     */
+    public int maxApPerScan;
+
+    /** The channels used in the scan.  If all channels should be used, {@code null} may be
+     *  specified.  If another application requests more channels or all channels, that
+     *  will take precedence.
+     */
+    public Collection<String> channelSet;
+
+    /** The time between the start of two sequential scans, in seconds.  If another
+     *  application requests more frequent scans, that will take precedence.  If this
+     * value is less than the duration of a scan, the next scan should start immediately.
+     */
+    public int scanIntervalSec;
+
+    /** The number of the best (strongest signal) APs for which the firmware will
+     *  attempt to get distance information (RTT).  Not all firmware supports this
+     *  feature, so it may be ignored.  If another application requests a greater
+     *  number, that will take precedence.
+     */
+    public int maxApForDistance;
+
+    public BatchedScanSettings() {
+        clear();
+    }
+
+    public void clear() {
+        maxScansPerBatch = UNSPECIFIED;
+        maxApPerScan = UNSPECIFIED;
+        channelSet = null;
+        scanIntervalSec = UNSPECIFIED;
+        maxApForDistance = UNSPECIFIED;
+    }
+
+    public BatchedScanSettings(BatchedScanSettings source) {
+        maxScansPerBatch = source.maxScansPerBatch;
+        maxApPerScan = source.maxApPerScan;
+        if (source.channelSet != null) {
+            channelSet = new ArrayList(source.channelSet);
+        }
+        scanIntervalSec = source.scanIntervalSec;
+        maxApForDistance = source.maxApForDistance;
+    }
+
+    private boolean channelSetIsValid() {
+        if (channelSet == null || channelSet.isEmpty()) return true;
+        for (String channel : channelSet) {
+            try {
+                int i = Integer.parseInt(channel);
+                if (i > 0 && i < 197) continue;
+            } catch (NumberFormatException e) {}
+            if (channel.equals("A") || channel.equals("B")) continue;
+            return false;
+        }
+        return true;
+    }
+    /** @hide */
+    public boolean isInvalid() {
+        if (maxScansPerBatch != UNSPECIFIED && (maxScansPerBatch < MIN_SCANS_PER_BATCH ||
+                maxScansPerBatch > MAX_SCANS_PER_BATCH)) return true;
+        if (maxApPerScan != UNSPECIFIED && (maxApPerScan < MIN_AP_PER_SCAN ||
+                maxApPerScan > MAX_AP_PER_SCAN)) return true;
+        if (channelSetIsValid() == false) return true;
+        if (scanIntervalSec != UNSPECIFIED && (scanIntervalSec < MIN_INTERVAL_SEC ||
+                scanIntervalSec > MAX_INTERVAL_SEC)) return true;
+        if (maxApForDistance != UNSPECIFIED && (maxApForDistance < MIN_AP_FOR_DISTANCE ||
+                maxApForDistance > MAX_AP_FOR_DISTANCE)) return true;
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof BatchedScanSettings == false) return false;
+        BatchedScanSettings o = (BatchedScanSettings)obj;
+        if (maxScansPerBatch != o.maxScansPerBatch ||
+              maxApPerScan != o.maxApPerScan ||
+              scanIntervalSec != o.scanIntervalSec ||
+              maxApForDistance != o.maxApForDistance) return false;
+        if (channelSet == null) {
+            return (o.channelSet == null);
+        }
+        return channelSet.equals(o.channelSet);
+    }
+
+    @Override
+    public int hashCode() {
+        return maxScansPerBatch +
+                (maxApPerScan * 3) +
+                (scanIntervalSec * 5) +
+                (maxApForDistance * 7) +
+                (channelSet.hashCode() * 11);
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        String none = "<none>";
+
+        sb.append("BatchScanSettings [maxScansPerBatch: ").
+                append(maxScansPerBatch == UNSPECIFIED ? none : maxScansPerBatch).
+                append(", maxApPerScan: ").append(maxApPerScan == UNSPECIFIED? none : maxApPerScan).
+                append(", scanIntervalSec: ").
+                append(scanIntervalSec == UNSPECIFIED ? none : scanIntervalSec).
+                append(", maxApForDistance: ").
+                append(maxApForDistance == UNSPECIFIED ? none : maxApForDistance).
+                append(", channelSet: ");
+        if (channelSet == null) {
+            sb.append("ALL");
+        } else {
+            sb.append("<");
+            for (String channel : channelSet) {
+                sb.append(" " + channel);
+            }
+            sb.append(">");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(maxScansPerBatch);
+        dest.writeInt(maxApPerScan);
+        dest.writeInt(scanIntervalSec);
+        dest.writeInt(maxApForDistance);
+        dest.writeInt(channelSet == null ? 0 : channelSet.size());
+        if (channelSet != null) {
+            for (String channel : channelSet) dest.writeString(channel);
+        }
+    }
+
+    /** Implement the Parcelable interface {@hide} */
+    public static final Creator<BatchedScanSettings> CREATOR =
+        new Creator<BatchedScanSettings>() {
+            public BatchedScanSettings createFromParcel(Parcel in) {
+                BatchedScanSettings settings = new BatchedScanSettings();
+                settings.maxScansPerBatch = in.readInt();
+                settings.maxApPerScan = in.readInt();
+                settings.scanIntervalSec = in.readInt();
+                settings.maxApForDistance = in.readInt();
+                int channelCount = in.readInt();
+                if (channelCount > 0) {
+                    settings.channelSet = new ArrayList(channelCount);
+                    while (channelCount-- > 0) {
+                        settings.channelSet.add(in.readString());
+                    }
+                }
+                return settings;
+            }
+
+            public BatchedScanSettings[] newArray(int size) {
+                return new BatchedScanSettings[size];
+            }
+        };
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 8103e84..c8cf323 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -16,8 +16,10 @@
 
 package android.net.wifi;
 
-import android.net.wifi.WifiInfo;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.ScanResult;
 import android.net.DhcpInfo;
 
@@ -114,5 +116,13 @@
     void enableTdls(String remoteIPAddress, boolean enable);
 
     void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable);
+
+    boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder);
+
+    void stopBatchedScan(in BatchedScanSettings requested, IBinder binder);
+
+    List<BatchedScanResult> getBatchedScanResults(String callingPackage);
+
+    boolean isBatchedScanSupported();
 }
 
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 9977419..12729d2a 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -54,7 +54,26 @@
      * Time Synchronization Function (tsf) timestamp in microseconds when
      * this result was last seen.
      */
-     public long timestamp;
+    public long timestamp;
+
+    /**
+     * The approximate distance to the AP in centimeter, if available.  Else
+     * {@link UNSPECIFIED}.
+     * {@hide}
+     */
+    public int distanceCm;
+
+    /**
+     * The standard deviation of the distance to the AP, if available.
+     * Else {@link UNSPECIFIED}.
+     * {@hide}
+     */
+    public int distanceSdCm;
+
+    /**
+     * {@hide}
+     */
+    public final static int UNSPECIFIED = -1;
 
     /** {@hide} */
     public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
@@ -66,8 +85,23 @@
         this.level = level;
         this.frequency = frequency;
         this.timestamp = tsf;
+        this.distanceCm = UNSPECIFIED;
+        this.distanceSdCm = UNSPECIFIED;
     }
 
+    /** {@hide} */
+    public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
+            long tsf, int distCm, int distSdCm) {
+        this.wifiSsid = wifiSsid;
+        this.SSID = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;
+        this.BSSID = BSSID;
+        this.capabilities = caps;
+        this.level = level;
+        this.frequency = frequency;
+        this.timestamp = tsf;
+        this.distanceCm = distCm;
+        this.distanceSdCm = distSdCm;
+    }
 
     /** copy constructor {@hide} */
     public ScanResult(ScanResult source) {
@@ -79,6 +113,8 @@
             level = source.level;
             frequency = source.frequency;
             timestamp = source.timestamp;
+            distanceCm = source.distanceCm;
+            distanceSdCm = source.distanceSdCm;
         }
     }
 
@@ -100,6 +136,11 @@
             append(", timestamp: ").
             append(timestamp);
 
+        sb.append(", distance: ").append((distanceCm != UNSPECIFIED ? distanceCm : "?")).
+                append("(cm)");
+        sb.append(", distanceSd: ").append((distanceSdCm != UNSPECIFIED ? distanceSdCm : "?")).
+                append("(cm)");
+
         return sb.toString();
     }
 
@@ -121,6 +162,8 @@
         dest.writeInt(level);
         dest.writeInt(frequency);
         dest.writeLong(timestamp);
+        dest.writeInt(distanceCm);
+        dest.writeInt(distanceSdCm);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -137,7 +180,9 @@
                     in.readString(),
                     in.readInt(),
                     in.readInt(),
-                    in.readLong()
+                    in.readLong(),
+                    in.readInt(),
+                    in.readInt()
                 );
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 6793710..01ca378 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -35,6 +35,7 @@
 import java.net.InetAddress;
 import java.util.concurrent.CountDownLatch;
 
+import com.android.internal.R;
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 
@@ -365,6 +366,14 @@
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String SCAN_RESULTS_AVAILABLE_ACTION = "android.net.wifi.SCAN_RESULTS";
     /**
+     * A batch of access point scans has been completed and the results areavailable.
+     * Call {@link #getBatchedScanResults()} to obtain the results.
+     * @hide pending review
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String BATCHED_SCAN_RESULTS_AVAILABLE_ACTION =
+            "android.net.wifi.BATCHED_RESULTS";
+    /**
      * The RSSI (signal strength) has changed.
      * @see #EXTRA_NEW_RSSI
      */
@@ -778,6 +787,59 @@
     }
 
     /**
+     * Request a batched scan for access points.  To end your requested batched scan,
+     * call stopBatchedScan with the same Settings.
+     *
+     * If there are mulitple requests for batched scans, the more demanding settings will
+     * take precidence.
+     *
+     * @param requested {@link BatchedScanSettings} the scan settings requested.
+     * @return false on known error
+     * @hide
+     */
+    public boolean requestBatchedScan(BatchedScanSettings requested) {
+        try {
+            return mService.requestBatchedScan(requested, new Binder());
+        } catch (RemoteException e) { return false; }
+    }
+
+    /**
+     * Check if the Batched Scan feature is supported.
+     *
+     * @return false if not supported.
+     * @hide
+     */
+    public boolean isBatchedScanSupported() {
+        try {
+            return mService.isBatchedScanSupported();
+        } catch (RemoteException e) { return false; }
+    }
+
+    /**
+     * End a requested batch scan for this applicaiton.  Note that batched scan may
+     * still occur if other apps are using them.
+     * @hide
+     */
+    public void stopBatchedScan(BatchedScanSettings requested) {
+        try {
+            mService.stopBatchedScan(requested, new Binder());
+        } catch (RemoteException e) {}
+    }
+
+    /**
+     * Retrieve the latest batched scan result.  This should be called immediately after
+     * {@link BATCHED_SCAN_RESULTS_AVAILABLE_ACTION} is received.
+     * @hide
+     */
+    public List<BatchedScanResult> getBatchedScanResults() {
+        try {
+            return mService.getBatchedScanResults(mContext.getBasePackageName());
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Return dynamic information about the current Wi-Fi connection, if any is active.
      * @return the Wi-Fi information, contained in {@link WifiInfo}.
      */
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 92b8e46..f6d5c98 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -514,6 +514,12 @@
                     if (space != -1) {
                         String iface = eventStr.substring(7,space);
                         m = mWifiMonitorSingleton.getMonitor(iface);
+                        if (m == null && iface.startsWith("p2p-")) {
+                            // p2p interfaces are created dynamically, but we have
+                            // only one P2p state machine monitoring all of them; look
+                            // for it explicitly, and send messages there ..
+                            m = mWifiMonitorSingleton.getMonitor("p2p0");
+                        }
                         if (m != null) {
                             if (m.mMonitoring) {
                                 mStateMachine = m.mWifiStateMachine;
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index d30c7cf..0359076 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -219,6 +219,40 @@
         return doStringCommand("BSS RANGE=" + sid + "- MASK=0x21987");
     }
 
+    /**
+     * Format of command
+     * DRIVER WLS_BATCHING SET SCAN_FRQ=x BESTN=y CHANNEL=<z, w, t> RTT=s
+     * where x is an ascii representation of an integer number of seconds between scans
+     *       y is an ascii representation of an integer number of the max AP to remember per scan
+     *       z, w, t represent a 1..n size list of channel numbers and/or 'A', 'B' values
+     *           indicating entire ranges of channels
+     *       s is an ascii representation of an integer number of highest-strength AP
+     *           for which we'd like approximate distance reported
+     *
+     * The return value is an ascii integer representing a guess of the number of scans
+     * the firmware can remember before it runs out of buffer space or -1 on error
+     */
+    public String setBatchedScanSettings(BatchedScanSettings settings) {
+        if (settings == null) return doStringCommand("DRIVER WLS_BATCHING STOP");
+        String cmd = "DRIVER WLS_BATCHING SET SCAN_FRQ=" + settings.scanIntervalSec;
+        if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) {
+            cmd += " BESTN " + settings.maxApPerScan;
+        }
+        if (settings.channelSet != null && !settings.channelSet.isEmpty()) {
+            cmd += " CHANNEL=<";
+            for (String channel : settings.channelSet) cmd += " " + channel;
+            cmd += ">";
+        }
+        if (settings.maxApForDistance != BatchedScanSettings.UNSPECIFIED) {
+            cmd += " RTT=" + settings.maxApForDistance;
+        }
+        return doStringCommand(cmd);
+    }
+
+    public String getBatchedScanResults() {
+        return doStringCommand("DRIVER WLS_BATCHING GET");
+    }
+
     public boolean startDriver() {
         return doBooleanCommand("DRIVER START");
     }
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 1fcd609..764c00a 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -123,6 +123,11 @@
     private static final int SCAN_RESULT_CACHE_SIZE = 80;
     private final LruCache<String, ScanResult> mScanResultCache;
 
+    /* Batch scan results */
+    private final List<BatchedScanResult> mBatchedScanResults =
+            new ArrayList<BatchedScanResult>();
+    private int mBatchedScanOwnerUid = UNKNOWN_SCAN_SOURCE;
+
     /* Chipset supports background scan */
     private final boolean mBackgroundScanSupported;
 
@@ -211,6 +216,7 @@
     private AlarmManager mAlarmManager;
     private PendingIntent mScanIntent;
     private PendingIntent mDriverStopIntent;
+    private PendingIntent mBatchedScanIntervalIntent;
 
     /* Tracks current frequency mode */
     private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
@@ -356,6 +362,13 @@
 
     public static final int CMD_BOOT_COMPLETED            = BASE + 134;
 
+    /* change the batch scan settings.
+     * arg1 = responsible UID
+     * obj = the new settings
+     */
+    public static final int CMD_SET_BATCH_SCAN            = BASE + 135;
+    public static final int CMD_START_NEXT_BATCHED_SCAN   = BASE + 136;
+
     public static final int CONNECT_MODE                   = 1;
     public static final int SCAN_ONLY_MODE                 = 2;
     public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE   = 3;
@@ -520,6 +533,8 @@
     private static final String ACTION_DELAYED_DRIVER_STOP =
         "com.android.server.WifiManager.action.DELAYED_DRIVER_STOP";
 
+    private static final String ACTION_REFRESH_BATCHED_SCAN =
+            "com.android.server.WifiManager.action.REFRESH_BATCHED_SCAN";
     /**
      * Keep track of whether WIFI is running.
      */
@@ -542,6 +557,9 @@
 
     private final IBatteryStats mBatteryStats;
 
+    private BatchedScanSettings mBatchedScanSettings = null;
+
+
     public WifiStateMachine(Context context, String wlanInterface) {
         super("WifiStateMachine");
         mContext = context;
@@ -577,6 +595,9 @@
         Intent scanIntent = new Intent(ACTION_START_SCAN, null);
         mScanIntent = PendingIntent.getBroadcast(mContext, SCAN_REQUEST, scanIntent, 0);
 
+        Intent batchedIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null);
+        mBatchedScanIntervalIntent = PendingIntent.getBroadcast(mContext, 0, batchedIntent, 0);
+
         mDefaultFrameworkScanIntervalMs = mContext.getResources().getInteger(
                 R.integer.config_wifi_framework_scan_interval);
 
@@ -614,22 +635,25 @@
                 },
                 new IntentFilter(ACTION_START_SCAN));
 
-        IntentFilter screenFilter = new IntentFilter();
-        screenFilter.addAction(Intent.ACTION_SCREEN_ON);
-        screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
-        BroadcastReceiver screenReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(ACTION_REFRESH_BATCHED_SCAN);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
 
-                if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                    handleScreenStateChanged(true);
-                } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                    handleScreenStateChanged(false);
-                }
-            }
-        };
-        mContext.registerReceiver(screenReceiver, screenFilter);
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            handleScreenStateChanged(true);
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            handleScreenStateChanged(false);
+                        } else if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) {
+                            startNextBatchedScanAsync();
+                        }
+                    }
+                }, filter);
 
         mContext.registerReceiver(
                 new BroadcastReceiver() {
@@ -738,6 +762,269 @@
         sendMessage(CMD_START_SCAN, callingUid, 0, workSource);
     }
 
+    /**
+     * start or stop batched scanning using the given settings
+     */
+    public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid) {
+        sendMessage(CMD_SET_BATCH_SCAN, callingUid, 0, settings);
+    }
+
+    public List<BatchedScanResult> syncGetBatchedScanResultsList() {
+        synchronized (mBatchedScanResults) {
+            List<BatchedScanResult> batchedScanList =
+                    new ArrayList<BatchedScanResult>(mBatchedScanResults.size());
+            for(BatchedScanResult result: mBatchedScanResults) {
+                batchedScanList.add(new BatchedScanResult(result));
+            }
+            return batchedScanList;
+        }
+    }
+
+    private void startBatchedScan() {
+        // first grab any existing data
+        retrieveBatchedScanData();
+
+        mAlarmManager.cancel(mBatchedScanIntervalIntent);
+
+        String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings);
+
+        try {
+            int expected = Integer.parseInt(scansExpected);
+            setNextBatchedAlarm(expected);
+        } catch (NumberFormatException e) {
+            loge("Exception parsing WifiNative.setBatchedScanSettings response " + e);
+        }
+    }
+
+    // called from BroadcastListener
+    private void startNextBatchedScanAsync() {
+        sendMessage(CMD_START_NEXT_BATCHED_SCAN);
+    }
+
+    private void startNextBatchedScan() {
+        // first grab any existing data
+        int nextCount = retrieveBatchedScanData();
+
+        setNextBatchedAlarm(nextCount);
+    }
+
+    // return true if new/different
+    private boolean recordBatchedScanSettings(BatchedScanSettings settings) {
+        if (DBG) log("set batched scan to " + settings);
+        if (settings != null) {
+            // TODO - noteBatchedScanStart(message.arg1);
+            if (settings.equals(mBatchedScanSettings)) return false;
+        } else {
+            if (mBatchedScanSettings == null) return false;
+            // TODO - noteBatchedScanStop(message.arg1);
+        }
+        mBatchedScanSettings = settings;
+        return true;
+    }
+
+    private void stopBatchedScan() {
+        mAlarmManager.cancel(mBatchedScanIntervalIntent);
+        retrieveBatchedScanData();
+        mWifiNative.setBatchedScanSettings(null);
+    }
+
+    private void setNextBatchedAlarm(int scansExpected) {
+
+        if (mBatchedScanSettings == null || scansExpected < 1) return;
+
+        if (mBatchedScanSettings.maxScansPerBatch < scansExpected) {
+            scansExpected = mBatchedScanSettings.maxScansPerBatch;
+        }
+
+        int secToFull = mBatchedScanSettings.scanIntervalSec;
+        secToFull *= scansExpected;
+
+        int debugPeriod = SystemProperties.getInt("wifi.batchedScan.pollPeriod", 0);
+        if (debugPeriod > 0) secToFull = debugPeriod;
+
+        // set the alarm to do the next poll.  We set it a little short as we'd rather
+        // wake up wearly than miss a scan due to buffer overflow
+        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+                + ((secToFull - (mBatchedScanSettings.scanIntervalSec / 2)) * 1000),
+                mBatchedScanIntervalIntent);
+    }
+
+    /**
+     * Start reading new scan data
+     * Data comes in as:
+     * "scancount=5\n"
+     * "nextcount=5\n"
+     *   "apcount=3\n"
+     *   "trunc\n" (optional)
+     *     "bssid=...\n"
+     *     "ssid=...\n"
+     *     "freq=...\n" (in Mhz)
+     *     "level=...\n"
+     *     "dist=...\n" (in cm)
+     *     "distsd=...\n" (standard deviation, in cm)
+     *     "===="
+     *     "bssid=...\n"
+     *     etc
+     *     "===="
+     *     "bssid=...\n"
+     *     etc
+     *     "%%%%"
+     *   "apcount=2\n"
+     *     "bssid=...\n"
+     *     etc
+     *     "%%%%
+     *   etc
+     *   "----"
+     */
+    private int retrieveBatchedScanData() {
+        String rawData = mWifiNative.getBatchedScanResults();
+        if (rawData == null) {
+            loge("Unexpected null BatchedScanResults");
+            return 0;
+        }
+
+        int nextCount = 0;
+        int scanCount = 0;
+        final String END_OF_SCAN = "====";
+        final String END_OF_BATCH = "%%%%";
+        final String END_OF_BATCHES = "----";
+        final String SCANCOUNT = "scancount=";
+        final String NEXTCOUNT = "nextcount=";
+        final String TRUNCATED = "trunc";
+        final String APCOUNT = "apcount=";
+        final String AGE = "age=";
+        final String DIST = "dist=";
+        final String DISTSD = "distsd=";
+
+        String splitData[] = rawData.split("\n");
+        int n = 0;
+        if (splitData[n].startsWith(SCANCOUNT)) {
+            try {
+                scanCount = Integer.parseInt(splitData[n++].substring(SCANCOUNT.length()));
+            } catch (NumberFormatException e) {}
+        }
+        if (scanCount == 0) {
+            loge("scanCount not found");
+            return 0;
+        }
+        if (splitData[n].startsWith(NEXTCOUNT)) {
+            try {
+                nextCount = Integer.parseInt(splitData[n++].substring(NEXTCOUNT.length()));
+            } catch (NumberFormatException e) {}
+        }
+        if (nextCount == 0) {
+            loge("nextCount not found");
+            return 0;
+        }
+
+        final Intent intent = new Intent(WifiManager.BATCHED_SCAN_RESULTS_AVAILABLE_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+
+        synchronized (mBatchedScanResults) {
+            mBatchedScanResults.clear();
+            BatchedScanResult batchedScanResult = new BatchedScanResult();
+
+            String bssid = null;
+            WifiSsid wifiSsid = null;
+            int level = 0;
+            int freq = 0;
+            int dist, distSd;
+            long tsf = 0;
+            dist = distSd = ScanResult.UNSPECIFIED;
+            long now = System.currentTimeMillis();
+
+            while (true) {
+                while (n < splitData.length) {
+                    if (splitData[n].equals(END_OF_BATCHES)) {
+                        if (++n != splitData.length) {
+                            loge("didn't consume " + (splitData.length-n));
+                        }
+                        if (mBatchedScanResults.size() > 0) {
+                            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                        }
+                        return nextCount;
+                    }
+                    if ((splitData[n].equals(END_OF_SCAN)) || splitData[n].equals(END_OF_BATCH)) {
+                        if (bssid != null) {
+                            batchedScanResult.scanResults.add(new ScanResult(
+                                    wifiSsid, bssid, "", level, freq, tsf, dist, distSd));
+                            wifiSsid = null;
+                            bssid = null;
+                            level = 0;
+                            freq = 0;
+                            tsf = 0;
+                            dist = distSd = ScanResult.UNSPECIFIED;
+                        }
+                        if (splitData[n].equals(END_OF_BATCH)) {
+                            if (batchedScanResult.scanResults.size() != 0) {
+                                mBatchedScanResults.add(batchedScanResult);
+                                batchedScanResult = new BatchedScanResult();
+                            } else {
+                                logd("Found empty batch");
+                            }
+                        }
+                        n++;
+                    } else if (splitData[n].equals(BSSID_STR)) {
+                        bssid = splitData[n++].substring(BSSID_STR.length());
+                    } else if (splitData[n].equals(FREQ_STR)) {
+                        try {
+                            freq = Integer.parseInt(splitData[n++].substring(FREQ_STR.length()));
+                        } catch (NumberFormatException e) {
+                            loge("Invalid freqency: " + splitData[n-1]);
+                            freq = 0;
+                        }
+                    } else if (splitData[n].equals(AGE)) {
+                        try {
+                            tsf = now - Long.parseLong(splitData[n++].substring(AGE.length()));
+                        } catch (NumberFormatException e) {
+                            loge("Invalid timestamp: " + splitData[n-1]);
+                            tsf = 0;
+                        }
+                    } else if (splitData[n].equals(SSID_STR)) {
+                        wifiSsid = WifiSsid.createFromAsciiEncoded(
+                                splitData[n++].substring(SSID_STR.length()));
+                    } else if (splitData[n].equals(LEVEL_STR)) {
+                        try {
+                            level = Integer.parseInt(splitData[n++].substring(LEVEL_STR.length()));
+                            if (level > 0) level -= 256;
+                        } catch (NumberFormatException e) {
+                            loge("Invalid level: " + splitData[n-1]);
+                            level = 0;
+                        }
+                    } else if (splitData[n].equals(DIST)) {
+                        try {
+                            dist = Integer.parseInt(splitData[n++].substring(DIST.length()));
+                        } catch (NumberFormatException e) {
+                            loge("Invalid distance: " + splitData[n-1]);
+                            dist = ScanResult.UNSPECIFIED;
+                        }
+                    } else if (splitData[n].equals(DISTSD)) {
+                        try {
+                            distSd = Integer.parseInt(splitData[n++].substring(DISTSD.length()));
+                        } catch (NumberFormatException e) {
+                            loge("Invalid distanceSd: " + splitData[n-1]);
+                            distSd = ScanResult.UNSPECIFIED;
+                        }
+                    }
+                }
+                rawData = mWifiNative.getBatchedScanResults();
+                if (rawData == null) {
+                    loge("Unexpected null BatchedScanResults");
+                    return nextCount;
+                }
+                splitData = rawData.split("\n");
+                if (splitData.length == 0 || splitData[0].equals("ok")) {
+                    loge("batch scan results just ended!");
+                    if (mBatchedScanResults.size() > 0) {
+                        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                    }
+                    return nextCount;
+                }
+                n = 0;
+            }
+        }
+    }
+
     // If workSource is not null, blame is given to it, otherwise blame is given to callingUid.
     private void noteScanStart(int callingUid, WorkSource workSource) {
         if (mScanWorkSource == null && (callingUid != UNKNOWN_SCAN_SOURCE || workSource != null)) {
@@ -1979,6 +2266,12 @@
                         sendMessageAtFrontOfQueue(CMD_SET_COUNTRY_CODE, countryCode);
                     }
                     break;
+                case CMD_SET_BATCH_SCAN:
+                    recordBatchedScanSettings((BatchedScanSettings)message.obj);
+                    break;
+                case CMD_START_NEXT_BATCHED_SCAN:
+                    startNextBatchedScan();
+                    break;
                     /* Discard */
                 case CMD_START_SCAN:
                 case CMD_START_SUPPLICANT:
@@ -2470,6 +2763,10 @@
                 mWifiNative.stopFilteringMulticastV4Packets();
             }
 
+            if (mBatchedScanSettings != null) {
+                startBatchedScan();
+            }
+
             if (mOperationalMode != CONNECT_MODE) {
                 mWifiNative.disconnect();
                 transitionTo(mScanModeState);
@@ -2511,6 +2808,10 @@
                     noteScanStart(message.arg1, (WorkSource) message.obj);
                     startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);
                     break;
+                case CMD_SET_BATCH_SCAN:
+                    recordBatchedScanSettings((BatchedScanSettings)message.obj);
+                    startBatchedScan();
+                    break;
                 case CMD_SET_COUNTRY_CODE:
                     String country = (String) message.obj;
                     if (DBG) log("set country code " + country);
@@ -2639,6 +2940,10 @@
             updateBatteryWorkSource(null);
             mScanResults = new ArrayList<ScanResult>();
 
+            if (mBatchedScanSettings != null) {
+                stopBatchedScan();
+            }
+
             final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);