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();
+ }
}