Merge "Add @UnsupportedAppUsage annotations"
diff --git a/api/current.txt b/api/current.txt
index 859b82d..ae10277 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -41870,8 +41870,6 @@
     field public static final java.lang.String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
     field public static final java.lang.String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
     field public static final java.lang.String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
-    field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int";
-    field public static final java.lang.String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int";
     field public static final java.lang.String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool";
     field public static final java.lang.String KEY_CARRIER_IMS_GBA_REQUIRED_BOOL = "carrier_ims_gba_required_bool";
     field public static final java.lang.String KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL = "carrier_instant_lettering_available_bool";
@@ -53020,7 +53018,15 @@
   public final class Magnifier {
     ctor public Magnifier(android.view.View);
     method public void dismiss();
+    method public float getCornerRadius();
+    method public int getDefaultHorizontalSourceToMagnifierOffset();
+    method public int getDefaultVerticalSourceToMagnifierOffset();
+    method public float getElevation();
     method public int getHeight();
+    method public android.graphics.Point getPosition();
+    method public int getSourceHeight();
+    method public android.graphics.Point getSourcePosition();
+    method public int getSourceWidth();
     method public int getWidth();
     method public float getZoom();
     method public void show(float, float);
@@ -53028,6 +53034,16 @@
     method public void update();
   }
 
+  public static class Magnifier.Builder {
+    ctor public Magnifier.Builder(android.view.View);
+    method public android.widget.Magnifier build();
+    method public android.widget.Magnifier.Builder setCornerRadius(float);
+    method public android.widget.Magnifier.Builder setDefaultSourceToMagnifierOffset(int, int);
+    method public android.widget.Magnifier.Builder setElevation(float);
+    method public android.widget.Magnifier.Builder setSize(int, int);
+    method public android.widget.Magnifier.Builder setZoom(float);
+  }
+
   public class MediaController extends android.widget.FrameLayout {
     ctor public MediaController(android.content.Context, android.util.AttributeSet);
     ctor public MediaController(android.content.Context, boolean);
diff --git a/api/test-current.txt b/api/test-current.txt
index 3a9d6a4..40aa440 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1598,7 +1598,6 @@
   public final class Magnifier {
     method public android.graphics.Bitmap getContent();
     method public static android.graphics.PointF getMagnifierDefaultSize();
-    method public android.graphics.Rect getWindowPositionOnScreen();
     method public void setOnOperationCompleteCallback(android.widget.Magnifier.Callback);
   }
 
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b7a5352..1108f93 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1009,6 +1009,13 @@
     public String appComponentFactory;
 
     /**
+     * Indicates whether this package requires access to non-SDK APIs. Only system apps
+     * and tests are allowed to use this property.
+     * @hide
+     */
+    public boolean usesNonSdkApi;
+
+    /**
      * The category of this app. Categories are used to cluster multiple apps
      * together into meaningful groups, such as when summarizing battery,
      * network, or disk usage. Apps should only define this value when they fit
@@ -1712,8 +1719,13 @@
     }
 
     private boolean isAllowedToUseHiddenApis() {
-        return isSignedWithPlatformKey()
-            || (isPackageWhitelistedForHiddenApis() && (isSystemApp() || isUpdatedSystemApp()));
+        if (isSignedWithPlatformKey()) {
+            return true;
+        } else if (isSystemApp() || isUpdatedSystemApp()) {
+            return usesNonSdkApi || isPackageWhitelistedForHiddenApis();
+        } else {
+            return false;
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 83757c4..8b058dc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3659,6 +3659,9 @@
             ai.appComponentFactory = buildClassName(ai.packageName, factory, outError);
         }
 
+        ai.usesNonSdkApi = sa.getBoolean(
+                com.android.internal.R.styleable.AndroidManifestApplication_usesNonSdkApi, false);
+
         if (outError[0] == null) {
             CharSequence pname;
             if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.FROYO) {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 47ce90b..4428598 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4898,23 +4898,23 @@
                     : controller.mEndHandle;
         }
 
-        private final Magnifier.Callback mHandlesVisibilityCallback = new Magnifier.Callback() {
-            @Override
-            public void onOperationComplete() {
-                final Point magnifierTopLeft = mMagnifierAnimator.mMagnifier.getWindowCoords();
-                if (magnifierTopLeft == null) {
-                    return;
-                }
-                final Rect magnifierRect = new Rect(magnifierTopLeft.x, magnifierTopLeft.y,
-                        magnifierTopLeft.x + mMagnifierAnimator.mMagnifier.getWidth(),
-                        magnifierTopLeft.y + mMagnifierAnimator.mMagnifier.getHeight());
-                setVisible(!handleOverlapsMagnifier(HandleView.this, magnifierRect));
-                final HandleView otherHandle = getOtherSelectionHandle();
-                if (otherHandle != null) {
-                    otherHandle.setVisible(!handleOverlapsMagnifier(otherHandle, magnifierRect));
-                }
+        private void updateHandlesVisibility() {
+            final Point magnifierTopLeft = mMagnifierAnimator.mMagnifier.getPosition();
+            if (magnifierTopLeft == null) {
+                return;
             }
-        };
+            final Rect surfaceInsets =
+                    mTextView.getViewRootImpl().mWindowAttributes.surfaceInsets;
+            magnifierTopLeft.offset(-surfaceInsets.left, -surfaceInsets.top);
+            final Rect magnifierRect = new Rect(magnifierTopLeft.x, magnifierTopLeft.y,
+                    magnifierTopLeft.x + mMagnifierAnimator.mMagnifier.getWidth(),
+                    magnifierTopLeft.y + mMagnifierAnimator.mMagnifier.getHeight());
+            setVisible(!handleOverlapsMagnifier(HandleView.this, magnifierRect));
+            final HandleView otherHandle = getOtherSelectionHandle();
+            if (otherHandle != null) {
+                otherHandle.setVisible(!handleOverlapsMagnifier(otherHandle, magnifierRect));
+            }
+        }
 
         protected final void updateMagnifier(@NonNull final MotionEvent event) {
             if (mMagnifierAnimator == null) {
@@ -4929,10 +4929,9 @@
                 mRenderCursorRegardlessTiming = true;
                 mTextView.invalidateCursorPath();
                 suspendBlink();
-                mMagnifierAnimator.mMagnifier
-                        .setOnOperationCompleteCallback(mHandlesVisibilityCallback);
 
                 mMagnifierAnimator.show(showPosInView.x, showPosInView.y);
+                updateHandlesVisibility();
             } else {
                 dismissMagnifier();
             }
@@ -4940,7 +4939,6 @@
 
         protected final void dismissMagnifier() {
             if (mMagnifierAnimator != null) {
-                mMagnifierAnimator.mMagnifier.setOnOperationCompleteCallback(null);
                 mMagnifierAnimator.dismiss();
                 mRenderCursorRegardlessTiming = false;
                 resumeBlink();
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 5734171..f82b17f 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -17,8 +17,10 @@
 package android.widget;
 
 import android.annotation.FloatRange;
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.Px;
 import android.annotation.TestApi;
 import android.annotation.UiThread;
 import android.content.Context;
@@ -74,7 +76,7 @@
     private final int mWindowWidth;
     // The height of the window containing the magnifier.
     private final int mWindowHeight;
-    // The zoom applied to the view region copied to the magnifier window.
+    // The zoom applied to the view region copied to the magnifier view.
     private final float mZoom;
     // The width of the content that will be copied to the magnifier.
     private final int mSourceWidth;
@@ -84,6 +86,10 @@
     private final float mWindowElevation;
     // The corner radius of the window containing the magnifier.
     private final float mWindowCornerRadius;
+    // The horizontal offset between the source and window coords when #show(float, float) is used.
+    private final int mDefaultHorizontalSourceToMagnifierOffset;
+    // The vertical offset between the source and window coords when #show(float, float) is used.
+    private final int mDefaultVerticalSourceToMagnifierOffset;
     // The parent surface for the magnifier surface.
     private SurfaceInfo mParentSurface;
     // The surface where the content will be copied from.
@@ -110,17 +116,27 @@
      * Initializes a magnifier.
      *
      * @param view the view for which this magnifier is attached
+     *
+     * @see Builder
      */
     public Magnifier(@NonNull View view) {
-        mView = Preconditions.checkNotNull(view);
-        final Context context = mView.getContext();
-        mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
-        mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
-        mWindowElevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
-        mWindowCornerRadius = getDeviceDefaultDialogCornerRadius();
-        mZoom = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
+        this(new Builder(view));
+    }
+
+    private Magnifier(@NonNull Builder params) {
+        // Copy params from builder.
+        mView = params.mView;
+        mWindowWidth = params.mWidth;
+        mWindowHeight = params.mHeight;
+        mZoom = params.mZoom;
         mSourceWidth = Math.round(mWindowWidth / mZoom);
         mSourceHeight = Math.round(mWindowHeight / mZoom);
+        mWindowElevation = params.mElevation;
+        mWindowCornerRadius = params.mCornerRadius;
+        mDefaultHorizontalSourceToMagnifierOffset =
+                params.mHorizontalDefaultSourceToMagnifierOffset;
+        mDefaultVerticalSourceToMagnifierOffset =
+                params.mVerticalDefaultSourceToMagnifierOffset;
         // The view's surface coordinates will not be updated until the magnifier is first shown.
         mViewCoordinatesInSurface = new int[2];
     }
@@ -130,53 +146,43 @@
     }
 
     /**
-     * Returns the device default theme dialog corner radius attribute.
-     * We retrieve this from the device default theme to avoid
-     * using the values set in the custom application themes.
-     */
-    private float getDeviceDefaultDialogCornerRadius() {
-        final Context deviceDefaultContext =
-                new ContextThemeWrapper(mView.getContext(), R.style.Theme_DeviceDefault);
-        final TypedArray ta = deviceDefaultContext.obtainStyledAttributes(
-                new int[]{android.R.attr.dialogCornerRadius});
-        final float dialogCornerRadius = ta.getDimension(0, 0);
-        ta.recycle();
-        return dialogCornerRadius;
-    }
-
-    /**
-     * Shows the magnifier on the screen.
+     * Shows the magnifier on the screen. The method takes the coordinates of the center
+     * of the content source going to be magnified and copied to the magnifier. The coordinates
+     * are relative to the top left corner of the magnified view. The magnifier will be
+     * positioned such that its center will be at the default offset from the center of the source.
+     * The default offset can be specified using the method
+     * {@link Builder#setDefaultSourceToMagnifierOffset(int, int)}. If the offset should
+     * be different across calls to this method, you should consider to use method
+     * {@link #show(float, float, float, float)} instead.
      *
-     * @param sourceCenterX horizontal coordinate of the center point of the source rectangle that
-     *        will be magnified and copied to the magnifier, relative to the view.
-     *        The parameter is clamped such that the copy rectangle fits inside [0, view width].
-     * @param sourceCenterY vertical coordinate of the center point of the source rectangle that
-     *        will be magnified and copied to the magnifier, relative to the view.
-     *        The parameter is clamped such that the copy rectangle fits inside [0, view height].
+     * @param sourceCenterX horizontal coordinate of the source center, relative to the view
+     * @param sourceCenterY vertical coordinate of the source center, relative to the view
+     *
+     * @see Builder#setDefaultSourceToMagnifierOffset(int, int)
+     * @see Builder#getDefaultHorizontalSourceToMagnifierOffset()
+     * @see Builder#getDefaultVerticalSourceToMagnifierOffset()
+     * @see #show(float, float, float, float)
      */
     public void show(@FloatRange(from = 0) float sourceCenterX,
             @FloatRange(from = 0) float sourceCenterY) {
-        final int verticalOffset = mView.getContext().getResources()
-                .getDimensionPixelSize(R.dimen.magnifier_offset);
-        show(sourceCenterX, sourceCenterY, sourceCenterX, sourceCenterY - verticalOffset);
+        show(sourceCenterX, sourceCenterY,
+                sourceCenterX + mDefaultHorizontalSourceToMagnifierOffset,
+                sourceCenterY + mDefaultVerticalSourceToMagnifierOffset);
     }
 
     /**
-     * Shows the magnifier on the screen at a position
-     * that is independent from its content position.
+     * Shows the magnifier on the screen at a position that is independent from its content
+     * position. The first two arguments represent the coordinates of the center of the
+     * content source going to be magnified and copied to the magnifier. The last two arguments
+     * represent the coordinates of the center of the magnifier itself. All four coordinates
+     * are relative to the top left corner of the magnified view. If you consider using this
+     * method such that the offset between the source center and the magnifier center coordinates
+     * remains constant, you should consider using method {@link #show(float, float)} instead.
      *
-     * @param sourceCenterX horizontal coordinate of the center point of the source rectangle that
-     *        will be magnified and copied to the magnifier, relative to the view.
-     *        The parameter is clamped such that the copy rectangle fits inside [0, view width].
-     * @param sourceCenterY vertical coordinate of the center point of the source rectangle that
-     *        will be magnified and copied to the magnifier, relative to the view.
-     *        The parameter is clamped such that the copy rectangle fits inside [0, view height].
-     * @param magnifierCenterX horizontal coordinate of the center point of the magnifier window
-     *        relative to the view. As the magnifier can be arbitrarily positioned, this can be
-     *        negative or larger than the view width.
-     * @param magnifierCenterY vertical coordinate of the center point of the magnifier window
-     *        relative to the view. As the magnifier can be arbitrarily positioned, this can be
-     *        negative or larger than the view height.
+     * @param sourceCenterX horizontal coordinate of the source center relative to the view
+     * @param sourceCenterY vertical coordinate of the source center, relative to the view
+     * @param magnifierCenterX horizontal coordinate of the magnifier center, relative to the view
+     * @param magnifierCenterY vertical coordinate of the magnifier center, relative to the view
      */
     public void show(@FloatRange(from = 0) float sourceCenterX,
             @FloatRange(from = 0) float sourceCenterY,
@@ -240,8 +246,9 @@
     }
 
     /**
-     * Forces the magnifier to update its content. It uses the previous coordinates passed to
-     * {@link #show(float, float)}. This only happens if the magnifier is currently showing.
+     * Asks the magnifier to update its content. It uses the previous coordinates passed to
+     * {@link #show(float, float)} or {@link #show(float, float, float, float)}. The
+     * method only has effect if the magnifier is currently showing.
      */
     public void update() {
         if (mWindow != null) {
@@ -253,41 +260,138 @@
     }
 
     /**
-     * @return The width of the magnifier window, in pixels.
+     * @return the width of the magnifier window, in pixels
+     * @see Magnifier.Builder#setSize(int, int)
      */
+    @Px
     public int getWidth() {
         return mWindowWidth;
     }
 
     /**
-     * @return The height of the magnifier window, in pixels.
+     * @return the height of the magnifier window, in pixels
+     * @see Magnifier.Builder#setSize(int, int)
      */
+    @Px
     public int getHeight() {
         return mWindowHeight;
     }
 
     /**
-     * @return The zoom applied to the magnified view region copied to the magnifier window.
+     * @return the initial width of the content magnified and copied to the magnifier, in pixels
+     * @see Magnifier.Builder#setSize(int, int)
+     * @see Magnifier.Builder#setZoom(float)
+     */
+    @Px
+    public int getSourceWidth() {
+        return mSourceWidth;
+    }
+
+    /**
+     * @return the initial height of the content magnified and copied to the magnifier, in pixels
+     * @see Magnifier.Builder#setSize(int, int)
+     * @see Magnifier.Builder#setZoom(float)
+     */
+    @Px
+    public int getSourceHeight() {
+        return mSourceHeight;
+    }
+
+    /**
+     * Returns the zoom to be applied to the magnified view region copied to the magnifier.
      * If the zoom is x and the magnifier window size is (width, height), the original size
-     * of the content copied in the magnifier will be (width / x, height / x).
+     * of the content being magnified will be (width / x, height / x).
+     * @return the zoom applied to the content
+     * @see Magnifier.Builder#setZoom(float)
      */
     public float getZoom() {
         return mZoom;
     }
 
     /**
-     * @hide
+     * @return the elevation set for the magnifier window, in pixels
+     * @see Magnifier.Builder#setElevation(float)
+     */
+    @Px
+    public float getElevation() {
+        return mWindowElevation;
+    }
+
+    /**
+     * @return the corner radius of the magnifier window, in pixels
+     * @see Magnifier.Builder#setCornerRadius(float)
+     */
+    @Px
+    public float getCornerRadius() {
+        return mWindowCornerRadius;
+    }
+
+    /**
+     * Returns the horizontal offset, in pixels, to be applied to the source center position
+     * to obtain the magnifier center position when {@link #show(float, float)} is called.
+     * The value is ignored when {@link #show(float, float, float, float)} is used instead.
      *
-     * @return The top left coordinates of the magnifier, relative to the parent window.
+     * @return the default horizontal offset between the source center and the magnifier
+     * @see Magnifier.Builder#setDefaultSourceToMagnifierOffset(int, int)
+     * @see Magnifier#show(float, float)
+     */
+    @Px
+    public int getDefaultHorizontalSourceToMagnifierOffset() {
+        return mDefaultHorizontalSourceToMagnifierOffset;
+    }
+
+    /**
+     * Returns the vertical offset, in pixels, to be applied to the source center position
+     * to obtain the magnifier center position when {@link #show(float, float)} is called.
+     * The value is ignored when {@link #show(float, float, float, float)} is used instead.
+     *
+     * @return the default vertical offset between the source center and the magnifier
+     * @see Magnifier.Builder#setDefaultSourceToMagnifierOffset(int, int)
+     * @see Magnifier#show(float, float)
+     */
+    @Px
+    public int getDefaultVerticalSourceToMagnifierOffset() {
+        return mDefaultVerticalSourceToMagnifierOffset;
+    }
+
+    /**
+     * Returns the top left coordinates of the magnifier, relative to the surface of the
+     * main application window. They will be determined by the coordinates of the last
+     * {@link #show(float, float)} or {@link #show(float, float, float, float)} call, adjusted
+     * to take into account any potential clamping behavior. The method can be used immediately
+     * after a #show call to find out where the magnifier will be positioned. However, the
+     * position of the magnifier will not be updated in the same frame due to the async
+     * copying of the content copying and of the magnifier rendering.
+     * The method will return {@code null} if #show has not yet been called, or if the last
+     * operation performed was a #dismiss.
+     *
+     * @return the top left coordinates of the magnifier
      */
     @Nullable
-    public Point getWindowCoords() {
+    public Point getPosition() {
         if (mWindow == null) {
             return null;
         }
-        final Rect surfaceInsets = mView.getViewRootImpl().mWindowAttributes.surfaceInsets;
-        return new Point(mWindow.mLastDrawContentPositionX - surfaceInsets.left,
-                mWindow.mLastDrawContentPositionY - surfaceInsets.top);
+        return new Point(getCurrentClampedWindowCoordinates());
+    }
+
+    /**
+     * Returns the top left coordinates of the magnifier source (i.e. the view region going to
+     * be magnified and copied to the magnifier), relative to the surface the content is copied
+     * from. The content will be copied:
+     * - if the magnified view is a {@link SurfaceView}, from the surface backing it
+     * - otherwise, from the surface of the main application window
+     * The method will return {@code null} if #show has not yet been called, or if the last
+     * operation performed was a #dismiss.
+     *
+     * @return the top left coordinates of the magnifier source
+     */
+    @Nullable
+    public Point getSourcePosition() {
+        if (mWindow == null) {
+            return null;
+        }
+        return new Point(mPixelCopyRequestRect.left, mPixelCopyRequestRect.top);
     }
 
     /**
@@ -750,6 +854,134 @@
         }
     }
 
+    /**
+     * Builder class for {@link Magnifier} objects.
+     */
+    public static class Builder {
+        private @NonNull View mView;
+        private @Px @IntRange(from = 0) int mWidth;
+        private @Px @IntRange(from = 0) int mHeight;
+        private float mZoom;
+        private @FloatRange(from = 0f) float mElevation;
+        private @FloatRange(from = 0f) float mCornerRadius;
+        private int mHorizontalDefaultSourceToMagnifierOffset;
+        private int mVerticalDefaultSourceToMagnifierOffset;
+
+        /**
+         * Construct a new builder for {@link Magnifier} objects.
+         * @param view the view this magnifier is attached to
+         */
+        public Builder(@NonNull View view) {
+            mView = Preconditions.checkNotNull(view);
+            applyDefaults();
+        }
+
+        private void applyDefaults() {
+            final Context context = mView.getContext();
+            final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Magnifier,
+                    R.attr.magnifierStyle, 0);
+            mWidth = a.getDimensionPixelSize(R.styleable.Magnifier_magnifierWidth, 0);
+            mHeight = a.getDimensionPixelSize(R.styleable.Magnifier_magnifierHeight, 0);
+            mElevation = a.getDimension(R.styleable.Magnifier_magnifierElevation, 0);
+            mCornerRadius = getDeviceDefaultDialogCornerRadius();
+            mZoom = a.getFloat(R.styleable.Magnifier_magnifierZoom, 0);
+            mHorizontalDefaultSourceToMagnifierOffset =
+                    a.getDimensionPixelSize(R.styleable.Magnifier_magnifierHorizontalOffset, 0);
+            mVerticalDefaultSourceToMagnifierOffset =
+                    a.getDimensionPixelSize(R.styleable.Magnifier_magnifierVerticalOffset, 0);
+            a.recycle();
+        }
+
+        /**
+         * Returns the device default theme dialog corner radius attribute.
+         * We retrieve this from the device default theme to avoid
+         * using the values set in the custom application themes.
+         */
+        private float getDeviceDefaultDialogCornerRadius() {
+            final Context deviceDefaultContext =
+                    new ContextThemeWrapper(mView.getContext(), R.style.Theme_DeviceDefault);
+            final TypedArray ta = deviceDefaultContext.obtainStyledAttributes(
+                    new int[]{android.R.attr.dialogCornerRadius});
+            final float dialogCornerRadius = ta.getDimension(0, 0);
+            ta.recycle();
+            return dialogCornerRadius;
+        }
+
+        /**
+         * Sets the size of the magnifier window, in pixels. Defaults to (100dp, 48dp).
+         * Note that the size of the content being magnified and copied to the magnifier
+         * will be computed as (window width / zoom, window height / zoom).
+         * @param width the window width to be set
+         * @param height the window height to be set
+         */
+        public Builder setSize(@Px @IntRange(from = 0) int width,
+                @Px @IntRange(from = 0) int height) {
+            Preconditions.checkArgumentPositive(width, "Width should be positive");
+            Preconditions.checkArgumentPositive(height, "Height should be positive");
+            mWidth = width;
+            mHeight = height;
+            return this;
+        }
+
+        /**
+         * Sets the zoom to be applied to the chosen content before being copied to the magnifier.
+         * A content of size (content_width, content_height) will be magnified to
+         * (content_width * zoom, content_height * zoom), which will coincide with the size
+         * of the magnifier. A zoom of 1 will translate to no magnification (the content will
+         * be just copied to the magnifier with no scaling). The zoom defaults to 1.25.
+         * @param zoom the zoom to be set
+         */
+        public Builder setZoom(@FloatRange(from = 0f) float zoom) {
+            Preconditions.checkArgumentPositive(zoom, "Zoom should be positive");
+            mZoom = zoom;
+            return this;
+        }
+
+        /**
+         * Sets the elevation of the magnifier window, in pixels. Defaults to 4dp.
+         * @param elevation the elevation to be set
+         */
+        public Builder setElevation(@Px @FloatRange(from = 0) float elevation) {
+            Preconditions.checkArgumentNonNegative(elevation, "Elevation should be non-negative");
+            mElevation = elevation;
+            return this;
+        }
+
+        /**
+         * Sets the corner radius of the magnifier window, in pixels.
+         * Defaults to the corner radius defined in the device default theme.
+         * @param cornerRadius the corner radius to be set
+         */
+        public Builder setCornerRadius(@Px @FloatRange(from = 0) float cornerRadius) {
+            Preconditions.checkArgumentNonNegative(cornerRadius,
+                    "Corner radius should be non-negative");
+            mCornerRadius = cornerRadius;
+            return this;
+        }
+
+        /**
+         * Sets an offset, in pixels, that should be added to the content source center to obtain
+         * the position of the magnifier window, when the {@link #show(float, float)}
+         * method is called. The offset is ignored when {@link #show(float, float, float, float)}
+         * is used. The offset can be negative, and it defaults to (0dp, -42dp).
+         * @param horizontalOffset the horizontal component of the offset
+         * @param verticalOffset the vertical component of the offset
+         */
+        public Builder setDefaultSourceToMagnifierOffset(@Px int horizontalOffset,
+                @Px int verticalOffset) {
+            mHorizontalDefaultSourceToMagnifierOffset = horizontalOffset;
+            mVerticalDefaultSourceToMagnifierOffset = verticalOffset;
+            return this;
+        }
+
+        /**
+         * Builds a {@link Magnifier} instance based on the configuration of this {@link Builder}.
+         */
+        public @NonNull Magnifier build() {
+            return new Magnifier(this);
+        }
+    }
+
     // The rest of the file consists of test APIs.
 
     /**
@@ -788,23 +1020,6 @@
     }
 
     /**
-     * @return the position of the magnifier window relative to the screen
-     *
-     * @hide
-     */
-    @TestApi
-    public Rect getWindowPositionOnScreen() {
-        final int[] viewLocationOnScreen = new int[2];
-        mView.getLocationOnScreen(viewLocationOnScreen);
-        final int[] viewLocationInSurface = new int[2];
-        mView.getLocationInSurface(viewLocationInSurface);
-
-        final int left = mWindowCoords.x + viewLocationOnScreen[0] - viewLocationInSurface[0];
-        final int top = mWindowCoords.y + viewLocationOnScreen[1] - viewLocationInSurface[1];
-        return new Rect(left, top, left + mWindowWidth, top + mWindowHeight);
-    }
-
-    /**
      * @return the size of the magnifier window in dp
      *
      * @hide
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 91c76af..2c6a0e0 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -192,7 +192,7 @@
     }
 
     /**
-     * Ensures that that the argument numeric value is non-negative.
+     * Ensures that that the argument numeric value is non-negative (greater than or equal to 0).
      *
      * @param value a numeric int value
      * @param errorMessage the exception message to use if the check fails
@@ -209,7 +209,7 @@
     }
 
     /**
-     * Ensures that that the argument numeric value is non-negative.
+     * Ensures that that the argument numeric value is non-negative (greater than or equal to 0).
      *
      * @param value a numeric int value
      *
@@ -225,7 +225,7 @@
     }
 
     /**
-     * Ensures that that the argument numeric value is non-negative.
+     * Ensures that that the argument numeric value is non-negative (greater than or equal to 0).
      *
      * @param value a numeric long value
      * @return the validated numeric value
@@ -240,7 +240,7 @@
     }
 
     /**
-     * Ensures that that the argument numeric value is non-negative.
+     * Ensures that that the argument numeric value is non-negative (greater than or equal to 0).
      *
      * @param value a numeric long value
      * @param errorMessage the exception message to use if the check fails
@@ -256,7 +256,7 @@
     }
 
     /**
-     * Ensures that that the argument numeric value is positive.
+     * Ensures that that the argument numeric value is positive (greater than 0).
      *
      * @param value a numeric int value
      * @param errorMessage the exception message to use if the check fails
@@ -272,6 +272,36 @@
     }
 
     /**
+     * Ensures that the argument floating point value is non-negative (greater than or equal to 0).
+     * @param value a floating point value
+     * @param errorMessage the exteption message to use if the check fails
+     * @return the validated numeric value
+     * @throws IllegalArgumentException if {@code value} was negative
+     */
+    public static float checkArgumentNonNegative(final float value, final String errorMessage) {
+        if (value < 0) {
+            throw new IllegalArgumentException(errorMessage);
+        }
+
+        return value;
+    }
+
+    /**
+     * Ensures that the argument floating point value is positive (greater than 0).
+     * @param value a floating point value
+     * @param errorMessage the exteption message to use if the check fails
+     * @return the validated numeric value
+     * @throws IllegalArgumentException if {@code value} was not positive
+     */
+    public static float checkArgumentPositive(final float value, final String errorMessage) {
+        if (value <= 0) {
+            throw new IllegalArgumentException(errorMessage);
+        }
+
+        return value;
+    }
+
+    /**
      * Ensures that the argument floating point value is a finite number.
      *
      * <p>A finite number is defined to be both representable (that is, not NaN) and
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2f710bf..64a9e6d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -759,6 +759,8 @@
         <attr name="contextPopupMenuStyle" format="reference" />
         <!-- Default StackView style. -->
         <attr name="stackViewStyle" format="reference" />
+        <!-- Magnifier style. -->
+        <attr name="magnifierStyle" format="reference" />
 
         <!-- Default style for the FragmentBreadCrumbs widget. This widget is deprecated
              starting in API level 21 ({@link android.os.Build.VERSION_CODES#.L}). -->
@@ -8921,4 +8923,13 @@
     </declare-styleable>
 
     <attr name="lockPatternStyle" format="reference" />
+
+    <declare-styleable name="Magnifier">
+        <attr name="magnifierWidth" format="dimension" />
+        <attr name="magnifierHeight" format="dimension" />
+        <attr name="magnifierZoom" format="float" />
+        <attr name="magnifierElevation" format="dimension" />
+        <attr name="magnifierVerticalOffset" format="dimension" />
+        <attr name="magnifierHorizontalOffset" format="dimension" />
+    </declare-styleable>
 </resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 74663c9..3c0e51e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1388,6 +1388,8 @@
          instantiates items without it.-->
     <attr name="appComponentFactory" format="string" />
 
+    <attr name="usesNonSdkApi" format="boolean" />
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -1561,6 +1563,9 @@
 
         <attr name="appComponentFactory" />
 
+        <!-- Declares that this application should be invoked without non-SDK API enforcement -->
+        <attr name="usesNonSdkApi" />
+
     </declare-styleable>
     <!-- The <code>permission</code> tag declares a security permission that can be
          used to control access from other packages to specific components or
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 73cb59e..097ce44 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -564,8 +564,9 @@
     <dimen name="magnifier_width">100dp</dimen>
     <dimen name="magnifier_height">48dp</dimen>
     <dimen name="magnifier_elevation">4dp</dimen>
-    <dimen name="magnifier_offset">42dp</dimen>
-    <item type="dimen" format="float" name="magnifier_zoom_scale">1.25</item>
+    <dimen name="magnifier_vertical_offset">-42dp</dimen>
+    <dimen name="magnifier_horizontal_offset">0dp</dimen>
+    <item type="dimen" format="float" name="magnifier_zoom">1.25</item>
 
     <dimen name="chooser_grid_padding">0dp</dimen>
     <!-- Spacing around the background change frome service to non-service -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d464ab7..fa31dce 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2911,6 +2911,11 @@
         <public name="supportsAmbientMode" />
     </public-group>
 
+    <public-group type="attr" first-id="0x0101058d">
+        <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
+        <public name="usesNonSdkApi" />
+    </public-group>
+
     <public-group type="style" first-id="0x010302e2">
     </public-group>
 
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index e1db71f..fafcf93 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -799,6 +799,15 @@
         <item name="textOff">@string/capital_off</item>
     </style>
 
+    <style name="Widget.Magnifier">
+        <item name="magnifierWidth">@dimen/magnifier_width</item>
+        <item name="magnifierHeight">@dimen/magnifier_height</item>
+        <item name="magnifierZoom">@dimen/magnifier_zoom</item>
+        <item name="magnifierElevation">@dimen/magnifier_elevation</item>
+        <item name="magnifierVerticalOffset">@dimen/magnifier_vertical_offset</item>
+        <item name="magnifierHorizontalOffset">@dimen/magnifier_horizontal_offset</item>
+    </style>
+
     <!-- Text Appearances -->
     <eat-comment />
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 363e1e1..6668c57 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2626,8 +2626,16 @@
   <java-symbol type="dimen" name="magnifier_width" />
   <java-symbol type="dimen" name="magnifier_height" />
   <java-symbol type="dimen" name="magnifier_elevation" />
-  <java-symbol type="dimen" name="magnifier_zoom_scale" />
-  <java-symbol type="dimen" name="magnifier_offset" />
+  <java-symbol type="dimen" name="magnifier_zoom" />
+  <java-symbol type="dimen" name="magnifier_vertical_offset" />
+  <java-symbol type="dimen" name="magnifier_horizontal_offset" />
+  <java-symbol type="attr" name="magnifierWidth" />
+  <java-symbol type="attr" name="magnifierHeight" />
+  <java-symbol type="attr" name="magnifierElevation" />
+  <java-symbol type="attr" name="magnifierZoom" />
+  <java-symbol type="attr" name="magnifierVerticalOffset" />
+  <java-symbol type="attr" name="magnifierHorizontalOffset" />
+  <java-symbol type="attr" name="magnifierStyle" />
 
   <java-symbol type="string" name="date_picker_prev_month_button" />
   <java-symbol type="string" name="date_picker_next_month_button" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 090f9af..3937af5 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -310,6 +310,7 @@
         <item name="activityChooserViewStyle">@style/Widget.ActivityChooserView</item>
         <item name="fragmentBreadCrumbsStyle">@style/Widget.FragmentBreadCrumbs</item>
         <item name="contextPopupMenuStyle">?attr/popupMenuStyle</item>
+        <item name="magnifierStyle">@style/Widget.Magnifier</item>
 
         <!-- Preference styles -->
         <item name="preferenceScreenStyle">@style/Preference.PreferenceScreen</item>
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bcfd5b9..fb0d9ad 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17060,8 +17060,8 @@
             activeInstr.mUiAutomationConnection = uiAutomationConnection;
             activeInstr.mResultClass = className;
 
-            boolean disableHiddenApiChecks =
-                    (flags & INSTRUMENTATION_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0;
+            boolean disableHiddenApiChecks = ai.usesNonSdkApi
+                    || (flags & INSTRUMENTATION_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0;
             if (disableHiddenApiChecks) {
                 enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS,
                         "disable hidden API checks");
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 1a4119c..5accb45 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -218,6 +218,7 @@
                     .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
                     .setDnsServers(addr)
                     .setServerAddr(new LinkAddress(addr, prefixLen))
+                    .setMetered(true)
                     .build();
             // TODO: also advertise link MTU
         } catch (DhcpServingParams.InvalidParameterException e) {
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 18f4bc7..41f2cc5 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -279,11 +279,13 @@
 
     public void ensureRecordExists(ComponentName component, Uri conditionId,
             IConditionProvider provider) {
-        // constructed by convention, make sure the record exists...
-        final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
-        if (r.info == null) {
-            // ... and is associated with the in-process service
-            r.info = checkServiceTokenLocked(provider);
+        synchronized (mMutex) {
+            // constructed by convention, make sure the record exists...
+            final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
+            if (r.info == null) {
+                // ... and is associated with the in-process service
+                r.info = checkServiceTokenLocked(provider);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index efc18ad..340ae0a 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -216,12 +216,14 @@
         }
 
         pw.println("    Live " + getCaption() + "s (" + mServices.size() + "):");
-        for (ManagedServiceInfo info : mServices) {
-            if (filter != null && !filter.matches(info.component)) continue;
-            pw.println("      " + info.component
-                    + " (user " + info.userid + "): " + info.service
-                    + (info.isSystem?" SYSTEM":"")
-                    + (info.isGuest(this)?" GUEST":""));
+        synchronized (mMutex) {
+            for (ManagedServiceInfo info : mServices) {
+                if (filter != null && !filter.matches(info.component)) continue;
+                pw.println("      " + info.component
+                        + " (user " + info.userid + "): " + info.service
+                        + (info.isSystem ? " SYSTEM" : "")
+                        + (info.isGuest(this) ? " GUEST" : ""));
+            }
         }
 
         pw.println("    Snoozed " + getCaption() + "s (" +
@@ -260,9 +262,11 @@
             cmpt.writeToProto(proto, ManagedServicesProto.ENABLED);
         }
 
-        for (ManagedServiceInfo info : mServices) {
-            if (filter != null && !filter.matches(info.component)) continue;
-            info.writeToProto(proto, ManagedServicesProto.LIVE_SERVICES, this);
+        synchronized (mMutex) {
+            for (ManagedServiceInfo info : mServices) {
+                if (filter != null && !filter.matches(info.component)) continue;
+                info.writeToProto(proto, ManagedServicesProto.LIVE_SERVICES, this);
+            }
         }
 
         for (ComponentName name : mSnoozingForCurrentProfiles) {
@@ -631,11 +635,13 @@
 
     public boolean isSameUser(IInterface service, int userId) {
         checkNotNull(service);
-        ManagedServiceInfo info = getServiceFromTokenLocked(service);
-        if (info != null) {
-            return info.isSameUser(userId);
+        synchronized (mMutex) {
+            ManagedServiceInfo info = getServiceFromTokenLocked(service);
+            if (info != null) {
+                return info.isSameUser(userId);
+            }
+            return false;
         }
-        return false;
     }
 
     public void unregisterService(IInterface service, int userid) {
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/services/net/java/android/net/dhcp/DhcpAckPacket.java
index df44b11..b2eb4e2 100644
--- a/services/net/java/android/net/dhcp/DhcpAckPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpAckPacket.java
@@ -30,8 +30,8 @@
     private final Inet4Address mSrcIp;
 
     DhcpAckPacket(int transId, short secs, boolean broadcast, Inet4Address serverAddress,
-                  Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
-        super(transId, secs, clientIp, yourIp, serverAddress, INADDR_ANY, clientMac, broadcast);
+            Inet4Address relayIp, Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
+        super(transId, secs, clientIp, yourIp, serverAddress, relayIp, clientMac, broadcast);
         mBroadcast = broadcast;
         mSrcIp = serverAddress;
     }
@@ -70,19 +70,8 @@
     void finishPacket(ByteBuffer buffer) {
         addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_ACK);
         addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-        addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime);
 
-        // the client should renew at 1/2 the lease-expiry interval
-        if (mLeaseTime != null) {
-            addTlv(buffer, DHCP_RENEWAL_TIME,
-                Integer.valueOf(mLeaseTime.intValue() / 2));
-        }
-
-        addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
-        addTlv(buffer, DHCP_ROUTER, mGateways);
-        addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
-        addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
-        addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
+        addCommonServerTlvs(buffer);
         addTlvEnd(buffer);
     }
 
diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/services/net/java/android/net/dhcp/DhcpNakPacket.java
index ef9af52..1da0b73 100644
--- a/services/net/java/android/net/dhcp/DhcpNakPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpNakPacket.java
@@ -26,9 +26,10 @@
     /**
      * Generates a NAK packet with the specified parameters.
      */
-    DhcpNakPacket(int transId, short secs, Inet4Address nextIp, Inet4Address relayIp,
-            byte[] clientMac, boolean broadcast) {
-        super(transId, secs, INADDR_ANY, INADDR_ANY, nextIp, relayIp, clientMac, broadcast);
+    DhcpNakPacket(int transId, short secs, Inet4Address relayIp, byte[] clientMac,
+            boolean broadcast) {
+        super(transId, secs, INADDR_ANY /* clientIp */, INADDR_ANY /* yourIp */,
+                INADDR_ANY /* nextIp */, relayIp, clientMac, broadcast);
     }
 
     public String toString() {
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
index 99154ef..0eba77e 100644
--- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
@@ -32,8 +32,8 @@
      * Generates a OFFER packet with the specified parameters.
      */
     DhcpOfferPacket(int transId, short secs, boolean broadcast, Inet4Address serverAddress,
-                    Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
-        super(transId, secs, clientIp, yourIp, INADDR_ANY, INADDR_ANY, clientMac, broadcast);
+            Inet4Address relayIp, Inet4Address clientIp, Inet4Address yourIp, byte[] clientMac) {
+        super(transId, secs, clientIp, yourIp, serverAddress, relayIp, clientMac, broadcast);
         mSrcIp = serverAddress;
     }
 
@@ -72,19 +72,8 @@
     void finishPacket(ByteBuffer buffer) {
         addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_OFFER);
         addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
-        addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime);
 
-        // the client should renew at 1/2 the lease-expiry interval
-        if (mLeaseTime != null) {
-            addTlv(buffer, DHCP_RENEWAL_TIME,
-                Integer.valueOf(mLeaseTime.intValue() / 2));
-        }
-
-        addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
-        addTlv(buffer, DHCP_ROUTER, mGateways);
-        addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
-        addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
-        addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
+        addCommonServerTlvs(buffer);
         addTlvEnd(buffer);
     }
 }
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 175e27e..595a129 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -184,6 +184,11 @@
     protected String mVendorInfo;
 
     /**
+     * Value of the vendor specific option used to indicate that the network is metered
+     */
+    public static final String VENDOR_INFO_ANDROID_METERED = "ANDROID_METERED";
+
+    /**
      * DHCP Optional Type: DHCP Requested IP Address
      */
     protected static final byte DHCP_REQUESTED_IP = 50;
@@ -677,6 +682,23 @@
         if (!TextUtils.isEmpty(hn)) addTlv(buf, DHCP_HOST_NAME, hn);
     }
 
+    protected void addCommonServerTlvs(ByteBuffer buf) {
+        addTlv(buf, DHCP_LEASE_TIME, mLeaseTime);
+        if (mLeaseTime != null && mLeaseTime != INFINITE_LEASE) {
+            // The client should renew at 1/2 the lease-expiry interval
+            addTlv(buf, DHCP_RENEWAL_TIME, (int) (Integer.toUnsignedLong(mLeaseTime) / 2));
+            // Default rebinding time is set as below by RFC2131
+            addTlv(buf, DHCP_REBINDING_TIME,
+                    (int) (Integer.toUnsignedLong(mLeaseTime) * 875L / 1000L));
+        }
+        addTlv(buf, DHCP_SUBNET_MASK, mSubnetMask);
+        addTlv(buf, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
+        addTlv(buf, DHCP_ROUTER, mGateways);
+        addTlv(buf, DHCP_DNS_SERVER, mDnsServers);
+        addTlv(buf, DHCP_DOMAIN_NAME, mDomainName);
+        addTlv(buf, DHCP_VENDOR_INFO, mVendorInfo);
+    }
+
     /**
      * Converts a MAC from an array of octets to an ASCII string.
      */
@@ -1085,7 +1107,7 @@
                 break;
             case DHCP_MESSAGE_TYPE_OFFER:
                 newPacket = new DhcpOfferPacket(
-                    transactionId, secs, broadcast, ipSrc, clientIp, yourIp, clientMac);
+                    transactionId, secs, broadcast, ipSrc, relayIp, clientIp, yourIp, clientMac);
                 break;
             case DHCP_MESSAGE_TYPE_REQUEST:
                 newPacket = new DhcpRequestPacket(
@@ -1098,11 +1120,11 @@
                 break;
             case DHCP_MESSAGE_TYPE_ACK:
                 newPacket = new DhcpAckPacket(
-                    transactionId, secs, broadcast, ipSrc, clientIp, yourIp, clientMac);
+                    transactionId, secs, broadcast, ipSrc, relayIp, clientIp, yourIp, clientMac);
                 break;
             case DHCP_MESSAGE_TYPE_NAK:
                 newPacket = new DhcpNakPacket(
-                        transactionId, secs, nextIp, relayIp, clientMac, broadcast);
+                        transactionId, secs, relayIp, clientMac, broadcast);
                 break;
             case DHCP_MESSAGE_TYPE_RELEASE:
                 if (serverIdentifier == null) {
@@ -1234,12 +1256,13 @@
      * parameters.
      */
     public static ByteBuffer buildOfferPacket(int encap, int transactionId,
-        boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
-        byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
-        List<Inet4Address> gateways, List<Inet4Address> dnsServers,
-        Inet4Address dhcpServerIdentifier, String domainName) {
+        boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp,
+        Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
+        Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
+        Inet4Address dhcpServerIdentifier, String domainName, boolean metered) {
         DhcpPacket pkt = new DhcpOfferPacket(
-            transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac);
+                transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
+                INADDR_ANY /* clientIp */, yourIp, mac);
         pkt.mGateways = gateways;
         pkt.mDnsServers = dnsServers;
         pkt.mLeaseTime = timeout;
@@ -1247,6 +1270,9 @@
         pkt.mServerIdentifier = dhcpServerIdentifier;
         pkt.mSubnetMask = netMask;
         pkt.mBroadcastAddress = bcAddr;
+        if (metered) {
+            pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
+        }
         return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
     }
 
@@ -1254,12 +1280,13 @@
      * Builds a DHCP-ACK packet from the required specified parameters.
      */
     public static ByteBuffer buildAckPacket(int encap, int transactionId,
-        boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
+        boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp,
         byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
         List<Inet4Address> gateways, List<Inet4Address> dnsServers,
-        Inet4Address dhcpServerIdentifier, String domainName) {
+        Inet4Address dhcpServerIdentifier, String domainName, boolean metered) {
         DhcpPacket pkt = new DhcpAckPacket(
-            transactionId, (short) 0, broadcast, serverIpAddr, INADDR_ANY, clientIpAddr, mac);
+                transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
+                INADDR_ANY /* clientIp */, yourIp, mac);
         pkt.mGateways = gateways;
         pkt.mDnsServers = dnsServers;
         pkt.mLeaseTime = timeout;
@@ -1267,6 +1294,9 @@
         pkt.mSubnetMask = netMask;
         pkt.mServerIdentifier = dhcpServerIdentifier;
         pkt.mBroadcastAddress = bcAddr;
+        if (metered) {
+            pkt.mVendorInfo = VENDOR_INFO_ANDROID_METERED;
+        }
         return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
     }
 
@@ -1274,10 +1304,11 @@
      * Builds a DHCP-NAK packet from the required specified parameters.
      */
     public static ByteBuffer buildNakPacket(int encap, int transactionId, Inet4Address serverIpAddr,
-            byte[] mac, boolean broadcast, String message) {
+            Inet4Address relayIp, byte[] mac, boolean broadcast, String message) {
         DhcpPacket pkt = new DhcpNakPacket(
-                transactionId, (short) 0, serverIpAddr, serverIpAddr, mac, broadcast);
+                transactionId, (short) 0, relayIp, mac, broadcast);
         pkt.mMessage = message;
+        pkt.mServerIdentifier = serverIpAddr;
         return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
     }
 
diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/services/net/java/android/net/dhcp/DhcpServer.java
index 095a5eb72..da8c8bb 100644
--- a/services/net/java/android/net/dhcp/DhcpServer.java
+++ b/services/net/java/android/net/dhcp/DhcpServer.java
@@ -351,12 +351,10 @@
                 mServingParams.getServerInet4Addr(), mServingParams.serverAddr.getPrefixLength());
         final ByteBuffer offerPacket = DhcpPacket.buildOfferPacket(
                 ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(),
-                lease.getNetAddr(), request.mClientMac, timeout,
-                prefixMask,
-                broadcastAddr,
-                new ArrayList<>(mServingParams.defaultRouters),
+                request.mRelayIp, lease.getNetAddr(), request.mClientMac, timeout, prefixMask,
+                broadcastAddr, new ArrayList<>(mServingParams.defaultRouters),
                 new ArrayList<>(mServingParams.dnsServers),
-                mServingParams.getServerInet4Addr(), null /* domainName */);
+                mServingParams.getServerInet4Addr(), null /* domainName */, mServingParams.metered);
 
         return transmitOfferOrAckPacket(offerPacket, request, lease, clientMac, broadcastFlag);
     }
@@ -368,12 +366,12 @@
         final boolean broadcastFlag = getBroadcastFlag(request, lease);
         final int timeout = getLeaseTimeout(lease);
         final ByteBuffer ackPacket = DhcpPacket.buildAckPacket(ENCAP_BOOTP, request.mTransId,
-                broadcastFlag, mServingParams.getServerInet4Addr(), lease.getNetAddr(),
-                request.mClientMac, timeout, mServingParams.getPrefixMaskAsAddress(),
-                mServingParams.getBroadcastAddress(),
+                broadcastFlag, mServingParams.getServerInet4Addr(), request.mRelayIp,
+                lease.getNetAddr(), request.mClientMac, timeout,
+                mServingParams.getPrefixMaskAsAddress(), mServingParams.getBroadcastAddress(),
                 new ArrayList<>(mServingParams.defaultRouters),
                 new ArrayList<>(mServingParams.dnsServers),
-                mServingParams.getServerInet4Addr(), null /* domainName */);
+                mServingParams.getServerInet4Addr(), null /* domainName */, mServingParams.metered);
 
         return transmitOfferOrAckPacket(ackPacket, request, lease, clientMac, broadcastFlag);
     }
@@ -383,7 +381,7 @@
         // Always set broadcast flag for NAK: client may not have a correct IP
         final ByteBuffer nakPacket = DhcpPacket.buildNakPacket(
                 ENCAP_BOOTP, request.mTransId, mServingParams.getServerInet4Addr(),
-                request.mClientMac, true /* broadcast */, message);
+                request.mRelayIp, request.mClientMac, true /* broadcast */, message);
 
         final Inet4Address dst = isEmpty(request.mRelayIp)
                 ? (Inet4Address) Inet4Address.ALL
diff --git a/services/net/java/android/net/dhcp/DhcpServingParams.java b/services/net/java/android/net/dhcp/DhcpServingParams.java
index 6d58bc6..df15ba1 100644
--- a/services/net/java/android/net/dhcp/DhcpServingParams.java
+++ b/services/net/java/android/net/dhcp/DhcpServingParams.java
@@ -76,6 +76,11 @@
     public final int linkMtu;
 
     /**
+     * Indicates whether the DHCP server should send the ANDROID_METERED vendor-specific option.
+     */
+    public final boolean metered;
+
+    /**
      * Checked exception thrown when some parameters used to build {@link DhcpServingParams} are
      * missing or invalid.
      */
@@ -88,13 +93,14 @@
     private DhcpServingParams(@NonNull LinkAddress serverAddr,
             @NonNull Set<Inet4Address> defaultRouters,
             @NonNull Set<Inet4Address> dnsServers, @NonNull Set<Inet4Address> excludedAddrs,
-            long dhcpLeaseTimeSecs, int linkMtu) {
+            long dhcpLeaseTimeSecs, int linkMtu, boolean metered) {
         this.serverAddr = serverAddr;
         this.defaultRouters = defaultRouters;
         this.dnsServers = dnsServers;
         this.excludedAddrs = excludedAddrs;
         this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
         this.linkMtu = linkMtu;
+        this.metered = metered;
     }
 
     @NonNull
@@ -134,6 +140,7 @@
         private Set<Inet4Address> excludedAddrs;
         private long dhcpLeaseTimeSecs;
         private int linkMtu = MTU_UNSET;
+        private boolean metered;
 
         /**
          * Set the server address and served prefix for the DHCP server.
@@ -248,6 +255,16 @@
         }
 
         /**
+         * Set whether the DHCP server should send the ANDROID_METERED vendor-specific option.
+         *
+         * <p>If not set, the default value is false.
+         */
+        public Builder setMetered(boolean metered) {
+            this.metered = metered;
+            return this;
+        }
+
+        /**
          * Create a new {@link DhcpServingParams} instance based on parameters set in the builder.
          *
          * <p>This method has no side-effects. If it does not throw, a valid
@@ -301,7 +318,7 @@
                     Collections.unmodifiableSet(new HashSet<>(defaultRouters)),
                     Collections.unmodifiableSet(new HashSet<>(dnsServers)),
                     Collections.unmodifiableSet(excl),
-                    dhcpLeaseTimeSecs, linkMtu);
+                    dhcpLeaseTimeSecs, linkMtu, metered);
         }
     }
 
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 09bc2f1..0cf1aec 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -520,19 +520,19 @@
             "carrier_wfc_supports_wifi_only_bool";
 
     /**
-     * Default mode for WFC over IMS on home network:
-     * <ul>
-     *   <li>0: Wi-Fi only
-     *   <li>1: prefer mobile network
-     *   <li>2: prefer Wi-Fi
-     * </ul>
+     * Default WFC_IMS_MODE for home network   0: WIFI_ONLY
+     *                                         1: CELLULAR_PREFERRED
+     *                                         2: WIFI_PREFERRED
+     * @hide
      */
     public static final String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT =
             "carrier_default_wfc_ims_mode_int";
 
     /**
-     * Default mode for WFC over IMS on roaming network.
-     * See {@link KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} for meaning of values.
+     * Default WFC_IMS_MODE for roaming
+     * See {@link KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} for valid values.
+     *
+     * @hide
      */
     public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT =
             "carrier_default_wfc_ims_roaming_mode_int";
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
index 050183c..312b3d1 100644
--- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
@@ -16,6 +16,8 @@
 
 package android.net.dhcp;
 
+import static android.net.NetworkUtils.getBroadcastAddress;
+import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
 import static android.net.dhcp.DhcpPacket.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -29,14 +31,15 @@
 import android.net.metrics.DhcpErrorEvent;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
-import android.system.OsConstants;
 
 import com.android.internal.util.HexDump;
 
+import java.io.ByteArrayOutputStream;
 import java.net.Inet4Address;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Random;
 
 import org.junit.Before;
@@ -47,13 +50,17 @@
 @SmallTest
 public class DhcpPacketTest {
 
-    private static Inet4Address SERVER_ADDR = v4Address("192.0.2.1");
-    private static Inet4Address CLIENT_ADDR = v4Address("192.0.2.234");
+    private static final Inet4Address SERVER_ADDR = v4Address("192.0.2.1");
+    private static final Inet4Address CLIENT_ADDR = v4Address("192.0.2.234");
+    private static final int PREFIX_LENGTH = 22;
+    private static final Inet4Address NETMASK = getPrefixMaskAsInet4Address(PREFIX_LENGTH);
+    private static final Inet4Address BROADCAST_ADDR = getBroadcastAddress(
+            SERVER_ADDR, PREFIX_LENGTH);
     // Use our own empty address instead of Inet4Address.ANY or INADDR_ANY to ensure that the code
     // doesn't use == instead of equals when comparing addresses.
-    private static Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0");
+    private static final Inet4Address ANY = (Inet4Address) v4Address("0.0.0.0");
 
-    private static byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+    private static final byte[] CLIENT_MAC = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
 
     private static final Inet4Address v4Address(String addrString) throws IllegalArgumentException {
         return (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
@@ -952,4 +959,96 @@
                 "\nActual:\n  " + Arrays.toString(actual);
         assertTrue(msg, Arrays.equals(expected, actual));
     }
+
+    public void checkBuildOfferPacket(int leaseTimeSecs) throws Exception {
+        final int renewalTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) / 2);
+        final int rebindingTime = (int) (Integer.toUnsignedLong(leaseTimeSecs) * 875 / 1000);
+        final int transactionId = 0xdeadbeef;
+
+        final ByteBuffer packet = DhcpPacket.buildOfferPacket(
+                DhcpPacket.ENCAP_BOOTP, transactionId, false /* broadcast */,
+                SERVER_ADDR, INADDR_ANY /* relayIp */, CLIENT_ADDR /* yourIp */,
+                CLIENT_MAC, leaseTimeSecs, NETMASK /* netMask */,
+                BROADCAST_ADDR /* bcAddr */, Collections.singletonList(SERVER_ADDR) /* gateways */,
+                Collections.singletonList(SERVER_ADDR) /* dnsServers */,
+                SERVER_ADDR /* dhcpServerIdentifier */, null /* domainName */, false /* metered */);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        // BOOTP headers
+        bos.write(new byte[] {
+                (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x00,
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // ciaddr
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        });
+        // yiaddr
+        bos.write(CLIENT_ADDR.getAddress());
+        // siaddr
+        bos.write(SERVER_ADDR.getAddress());
+        // giaddr
+        bos.write(INADDR_ANY.getAddress());
+        // chaddr
+        bos.write(CLIENT_MAC);
+
+        // Padding
+        bos.write(new byte[202]);
+
+        // Options
+        bos.write(new byte[]{
+                // Magic cookie 0x63825363.
+                (byte) 0x63, (byte) 0x82, (byte) 0x53, (byte) 0x63,
+                // Message type OFFER.
+                (byte) 0x35, (byte) 0x01, (byte) 0x02,
+        });
+        // Server ID
+        bos.write(new byte[] { (byte) 0x36, (byte) 0x04 });
+        bos.write(SERVER_ADDR.getAddress());
+        // Lease time
+        bos.write(new byte[] { (byte) 0x33, (byte) 0x04 });
+        bos.write(intToByteArray(leaseTimeSecs));
+        if (leaseTimeSecs != INFINITE_LEASE) {
+            // Renewal time
+            bos.write(new byte[]{(byte) 0x3a, (byte) 0x04});
+            bos.write(intToByteArray(renewalTime));
+            // Rebinding time
+            bos.write(new byte[]{(byte) 0x3b, (byte) 0x04});
+            bos.write(intToByteArray(rebindingTime));
+        }
+        // Subnet mask
+        bos.write(new byte[] { (byte) 0x01, (byte) 0x04 });
+        bos.write(NETMASK.getAddress());
+        // Broadcast address
+        bos.write(new byte[] { (byte) 0x1c, (byte) 0x04 });
+        bos.write(BROADCAST_ADDR.getAddress());
+        // Router
+        bos.write(new byte[] { (byte) 0x03, (byte) 0x04 });
+        bos.write(SERVER_ADDR.getAddress());
+        // Nameserver
+        bos.write(new byte[] { (byte) 0x06, (byte) 0x04 });
+        bos.write(SERVER_ADDR.getAddress());
+        // End options.
+        bos.write(0xff);
+
+        final byte[] expected = bos.toByteArray();
+        assertTrue((expected.length & 1) == 0);
+
+        final byte[] actual = new byte[packet.limit()];
+        packet.get(actual);
+        final String msg = "Expected:\n  " + HexDump.dumpHexString(expected) +
+                "\nActual:\n  " + HexDump.dumpHexString(actual);
+        assertTrue(msg, Arrays.equals(expected, actual));
+    }
+
+    @Test
+    public void testOfferPacket() throws Exception {
+        checkBuildOfferPacket(3600);
+        checkBuildOfferPacket(Integer.MAX_VALUE);
+        checkBuildOfferPacket(0x80000000);
+        checkBuildOfferPacket(INFINITE_LEASE);
+    }
+
+    private static byte[] intToByteArray(int val) {
+        return ByteBuffer.allocate(4).putInt(val).array();
+    }
 }