Merge "Fix activity not launching from recents after it was dismissed."
diff --git a/api/current.txt b/api/current.txt
index 1e99d56..49c1272 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -728,6 +728,7 @@
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field public static final int labelTextSize = 16843317; // 0x1010235
+ field public static final int languageTag = 16844041; // 0x1010509
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -20733,6 +20734,7 @@
public static class MediaRouter.RouteInfo {
method public android.media.MediaRouter.RouteCategory getCategory();
method public java.lang.CharSequence getDescription();
+ method public int getDeviceType();
method public android.media.MediaRouter.RouteGroup getGroup();
method public android.graphics.drawable.Drawable getIconDrawable();
method public java.lang.CharSequence getName();
@@ -20751,6 +20753,10 @@
method public void requestSetVolume(int);
method public void requestUpdateVolume(int);
method public void setTag(java.lang.Object);
+ field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
@@ -40243,6 +40249,7 @@
field public static final int AXIS_RX = 12; // 0xc
field public static final int AXIS_RY = 13; // 0xd
field public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
field public static final int AXIS_SIZE = 3; // 0x3
field public static final int AXIS_THROTTLE = 19; // 0x13
field public static final int AXIS_TILT = 25; // 0x19
@@ -43208,7 +43215,8 @@
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
method public int getIconResId();
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public java.lang.String getMode();
method public int getNameResId();
method public boolean isAsciiCapable();
@@ -43223,6 +43231,7 @@
method public android.view.inputmethod.InputMethodSubtype build();
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -43286,7 +43295,8 @@
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
diff --git a/api/system-current.txt b/api/system-current.txt
index 8be751c..0fe65c3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -822,6 +822,7 @@
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field public static final int labelTextSize = 16843317; // 0x1010235
+ field public static final int languageTag = 16844041; // 0x1010509
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -22026,6 +22027,7 @@
public static class MediaRouter.RouteInfo {
method public android.media.MediaRouter.RouteCategory getCategory();
method public java.lang.CharSequence getDescription();
+ method public int getDeviceType();
method public android.media.MediaRouter.RouteGroup getGroup();
method public android.graphics.drawable.Drawable getIconDrawable();
method public java.lang.CharSequence getName();
@@ -22044,6 +22046,10 @@
method public void requestSetVolume(int);
method public void requestUpdateVolume(int);
method public void setTag(java.lang.Object);
+ field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
@@ -42581,6 +42587,7 @@
field public static final int AXIS_RX = 12; // 0xc
field public static final int AXIS_RY = 13; // 0xd
field public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
field public static final int AXIS_SIZE = 3; // 0x3
field public static final int AXIS_THROTTLE = 19; // 0x13
field public static final int AXIS_TILT = 25; // 0x19
@@ -45549,7 +45556,8 @@
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
method public int getIconResId();
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public java.lang.String getMode();
method public int getNameResId();
method public boolean isAsciiCapable();
@@ -45564,6 +45572,7 @@
method public android.view.inputmethod.InputMethodSubtype build();
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -45627,7 +45636,8 @@
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
diff --git a/api/test-current.txt b/api/test-current.txt
index 3e455102..e343cfb 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -728,6 +728,7 @@
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field public static final int labelTextSize = 16843317; // 0x1010235
+ field public static final int languageTag = 16844041; // 0x1010509
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -20733,6 +20734,7 @@
public static class MediaRouter.RouteInfo {
method public android.media.MediaRouter.RouteCategory getCategory();
method public java.lang.CharSequence getDescription();
+ method public int getDeviceType();
method public android.media.MediaRouter.RouteGroup getGroup();
method public android.graphics.drawable.Drawable getIconDrawable();
method public java.lang.CharSequence getName();
@@ -20751,6 +20753,10 @@
method public void requestSetVolume(int);
method public void requestUpdateVolume(int);
method public void setTag(java.lang.Object);
+ field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
@@ -40245,6 +40251,7 @@
field public static final int AXIS_RX = 12; // 0xc
field public static final int AXIS_RY = 13; // 0xd
field public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SCROLL = 26; // 0x1a
field public static final int AXIS_SIZE = 3; // 0x3
field public static final int AXIS_THROTTLE = 19; // 0x13
field public static final int AXIS_TILT = 25; // 0x19
@@ -43210,7 +43217,8 @@
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
method public int getIconResId();
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public java.lang.String getMode();
method public int getNameResId();
method public boolean isAsciiCapable();
@@ -43225,6 +43233,7 @@
method public android.view.inputmethod.InputMethodSubtype build();
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -43288,7 +43297,8 @@
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 1f378da..175b979 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.SystemApi;
+import android.os.Build;
import android.os.Bundle;
/**
@@ -28,15 +29,28 @@
@SystemApi
public class BroadcastOptions {
private long mTemporaryAppWhitelistDuration;
+ private int mMinManifestReceiverApiLevel = 0;
+ private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
/**
* How long to temporarily put an app on the power whitelist when executing this broadcast
* to it.
- * @hide
*/
- public static final String KEY_TEMPORARY_APP_WHITELIST_DURATION
+ static final String KEY_TEMPORARY_APP_WHITELIST_DURATION
= "android:broadcast.temporaryAppWhitelistDuration";
+ /**
+ * Corresponds to {@link #setMinManifestReceiverApiLevel}.
+ */
+ static final String KEY_MIN_MANIFEST_RECEIVER_API_LEVEL
+ = "android:broadcast.minManifestReceiverApiLevel";
+
+ /**
+ * Corresponds to {@link #setMaxManifestReceiverApiLevel}.
+ */
+ static final String KEY_MAX_MANIFEST_RECEIVER_API_LEVEL
+ = "android:broadcast.maxManifestReceiverApiLevel";
+
public static BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
@@ -48,6 +62,9 @@
/** @hide */
public BroadcastOptions(Bundle opts) {
mTemporaryAppWhitelistDuration = opts.getLong(KEY_TEMPORARY_APP_WHITELIST_DURATION);
+ mMinManifestReceiverApiLevel = opts.getInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, 0);
+ mMaxManifestReceiverApiLevel = opts.getInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL,
+ Build.VERSION_CODES.CUR_DEVELOPMENT);
}
/**
@@ -68,10 +85,46 @@
}
/**
+ * Set the minimum target API level of receivers of the broadcast. If an application
+ * is targeting an API level less than this, the broadcast will not be delivered to
+ * them. This only applies to receivers declared in the app's AndroidManifest.xml.
+ * @hide
+ */
+ public void setMinManifestReceiverApiLevel(int apiLevel) {
+ mMinManifestReceiverApiLevel = apiLevel;
+ }
+
+ /**
+ * Return {@link #setMinManifestReceiverApiLevel}.
+ * @hide
+ */
+ public int getMinManifestReceiverApiLevel() {
+ return mMinManifestReceiverApiLevel;
+ }
+
+ /**
+ * Set the maximum target API level of receivers of the broadcast. If an application
+ * is targeting an API level greater than this, the broadcast will not be delivered to
+ * them. This only applies to receivers declared in the app's AndroidManifest.xml.
+ * @hide
+ */
+ public void setMaxManifestReceiverApiLevel(int apiLevel) {
+ mMaxManifestReceiverApiLevel = apiLevel;
+ }
+
+ /**
+ * Return {@link #setMaxManifestReceiverApiLevel}.
+ * @hide
+ */
+ public int getMaxManifestReceiverApiLevel() {
+ return mMaxManifestReceiverApiLevel;
+ }
+
+ /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
- * Note that the returned Bundle is still owned by the ActivityOptions
+ * Note that the returned Bundle is still owned by the BroadcastOptions
* object; you must not modify it, but can supply it to the sendBroadcast
* methods that take an options Bundle.
*/
@@ -80,6 +133,12 @@
if (mTemporaryAppWhitelistDuration > 0) {
b.putLong(KEY_TEMPORARY_APP_WHITELIST_DURATION, mTemporaryAppWhitelistDuration);
}
+ if (mMinManifestReceiverApiLevel != 0) {
+ b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
+ }
+ if (mMaxManifestReceiverApiLevel != Build.VERSION_CODES.CUR_DEVELOPMENT) {
+ b.putInt(KEY_MAX_MANIFEST_RECEIVER_API_LEVEL, mMaxManifestReceiverApiLevel);
+ }
return b.isEmpty() ? null : b;
}
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d90ed9f6..569ab11 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1093,7 +1093,23 @@
intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true, user.getIdentifier());
+ Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, true,
+ user.getIdentifier());
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failure from system", e);
+ }
+ }
+
+ @Override
+ @Deprecated
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.prepareToLeaveProcess();
+ ActivityManagerNative.getDefault().broadcastIntent(
+ mMainThread.getApplicationThread(), intent, resolvedType, null,
+ Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options, false, true,
+ user.getIdentifier());
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 099a5fe..e7dd5ff 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -46,6 +46,7 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.NotificationHeaderView;
@@ -64,6 +65,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* A class that represents how a persistent notification is to be presented to
@@ -1693,11 +1695,21 @@
bigContentView = null;
headsUpContentView = null;
mLargeIcon = null;
- if (extras != null) {
- extras.remove(Notification.EXTRA_LARGE_ICON);
- extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
- extras.remove(Notification.EXTRA_PICTURE);
- extras.remove(Notification.EXTRA_BIG_TEXT);
+ if (extras != null && !extras.isEmpty()) {
+ final Set<String> keyset = extras.keySet();
+ final int N = keyset.size();
+ final String[] keys = keyset.toArray(new String[N]);
+ for (int i=0; i<N; i++) {
+ final String key = keys[i];
+ final Object obj = extras.get(key);
+ if (obj != null &&
+ ( obj instanceof Parcelable
+ || obj instanceof Parcelable[]
+ || obj instanceof SparseArray
+ || obj instanceof ArrayList)) {
+ extras.remove(key);
+ }
+ }
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c61f204..38a4475 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2116,6 +2116,14 @@
UserHandle user);
/**
+ * @hide
+ * This is just here for sending CONNECTIVITY_ACTION.
+ */
+ @Deprecated
+ public abstract void sendStickyBroadcastAsUser(@RequiresPermission Intent intent,
+ UserHandle user, Bundle options);
+
+ /**
* <p>Version of
* {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)}
* that allows you to specify the
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index e49e771..1a3d262 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -536,6 +536,13 @@
mBase.sendStickyBroadcastAsUser(intent, user);
}
+ /** @hide */
+ @Override
+ @Deprecated
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
+ mBase.sendStickyBroadcastAsUser(intent, user, options);
+ }
+
@Override
@Deprecated
public void sendStickyOrderedBroadcastAsUser(Intent intent,
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 2459cfa..57fe131 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -20,7 +20,6 @@
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -222,34 +221,26 @@
return lineEnd(widget, buffer);
}
- private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) {
- return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer);
- }
-
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1;
int initialScrollY = -1;
final int action = event.getAction();
- final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
if (action == MotionEvent.ACTION_UP) {
initialScrollX = Touch.getInitialScrollX(widget, buffer);
initialScrollY = Touch.getInitialScrollY(widget, buffer);
}
- boolean wasTouchSelecting = isTouchSelecting(isMouse, buffer);
+ boolean wasTouchSelecting = isSelecting(buffer);
boolean handled = Touch.onTouchEvent(widget, buffer, event);
- if (widget.didTouchFocusSelect() && !isMouse) {
+ if (widget.didTouchFocusSelect()) {
return handled;
}
if (action == MotionEvent.ACTION_DOWN) {
- // Capture the mouse pointer down location to ensure selection starts
- // right under the mouse (and is not influenced by cursor location).
- // The code below needs to run for mouse events.
// For touch events, the code should run only when selection is active.
- if (isMouse || isTouchSelecting(isMouse, buffer)) {
+ if (isSelecting(buffer)) {
if (!widget.isFocused()) {
if (!widget.requestFocus()) {
return handled;
@@ -265,15 +256,8 @@
}
} else if (widget.isFocused()) {
if (action == MotionEvent.ACTION_MOVE) {
- // Cursor can be active at any location in the text while mouse pointer can start
- // selection from a totally different location. Use LAST_TAP_DOWN span to ensure
- // text selection will start from mouse pointer location.
- final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
- if (isMouse && Touch.isSelectionStarted(buffer)) {
- Selection.setSelection(buffer, startOffset);
- }
-
- if (isTouchSelecting(isMouse, buffer) && handled) {
+ if (isSelecting(buffer) && handled) {
+ final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
// Before selecting, make sure we've moved out of the "slop".
// handled will be true, if we're in select mode AND we're
// OUT of the slop
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index fee7377..d9068dc 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -119,18 +119,12 @@
ds = buffer.getSpans(0, buffer.length(), DragState.class);
if (ds.length > 0) {
- ds[0].mIsSelectionStarted = false;
-
if (ds[0].mFarEnough == false) {
int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
if (Math.abs(event.getX() - ds[0].mX) >= slop ||
Math.abs(event.getY() - ds[0].mY) >= slop) {
ds[0].mFarEnough = true;
- if (event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
- ds[0].mIsActivelySelecting = true;
- ds[0].mIsSelectionStarted = true;
- }
}
}
@@ -142,13 +136,9 @@
|| MetaKeyKeyListener.getMetaState(buffer,
MetaKeyKeyListener.META_SELECTING) != 0;
- if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
- ds[0].mIsActivelySelecting = false;
- }
-
float dx;
float dy;
- if (cap && event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
+ if (cap) {
// if we're selecting, we want the scroll to go in
// the direction of the drag
dx = event.getX() - ds[0].mX;
@@ -160,7 +150,6 @@
ds[0].mX = event.getX();
ds[0].mY = event.getY();
- int nx = widget.getScrollX() + (int) dx;
int ny = widget.getScrollY() + (int) dy;
int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
@@ -172,10 +161,6 @@
int oldX = widget.getScrollX();
int oldY = widget.getScrollY();
- if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
- scrollTo(widget, layout, nx, ny);
- }
-
// If we actually scrolled, then cancel the up action.
if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
widget.cancelLongPress();
@@ -207,37 +192,6 @@
return ds.length > 0 ? ds[0].mScrollY : -1;
}
- /**
- * Checks if selection is still active.
- * This is useful for extending Selection span on buffer.
- * @param buffer The text buffer.
- * @return true if buffer has been marked for selection.
- *
- * @hide
- */
- static boolean isActivelySelecting(Spannable buffer) {
- DragState[] ds;
- ds = buffer.getSpans(0, buffer.length(), DragState.class);
-
- return ds.length > 0 && ds[0].mIsActivelySelecting;
- }
-
- /**
- * Checks if selection has begun (are we out of slop?).
- * Note: DragState.mIsSelectionStarted goes back to false with the very next event.
- * This is useful for starting Selection span on buffer.
- * @param buffer The text buffer.
- * @return true if selection has started on the buffer.
- *
- * @hide
- */
- static boolean isSelectionStarted(Spannable buffer) {
- DragState[] ds;
- ds = buffer.getSpans(0, buffer.length(), DragState.class);
-
- return ds.length > 0 && ds[0].mIsSelectionStarted;
- }
-
private static class DragState implements NoCopySpan {
public float mX;
public float mY;
@@ -245,8 +199,6 @@
public int mScrollY;
public boolean mFarEnough;
public boolean mUsed;
- public boolean mIsActivelySelecting;
- public boolean mIsSelectionStarted;
public DragState(float x, float y, int scrollX, int scrollY) {
mX = x;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 527d7e5..0195dec 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -973,7 +973,6 @@
* </p>
*
* @see #getAxisValue(int, int)
- * {@hide}
*/
public static final int AXIS_SCROLL = 26;
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index fbaf51c..a42f4d9 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -16,6 +16,7 @@
package android.view.inputmethod;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -50,6 +51,7 @@
*
* @attr ref android.R.styleable#InputMethod_Subtype_label
* @attr ref android.R.styleable#InputMethod_Subtype_icon
+ * @attr ref android.R.styleable#InputMethod_Subtype_languageTag
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
@@ -60,6 +62,7 @@
*/
public final class InputMethodSubtype implements Parcelable {
private static final String TAG = InputMethodSubtype.class.getSimpleName();
+ private static final String LANGUAGE_TAG_NONE = "";
private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
// TODO: remove this
@@ -74,6 +77,7 @@
private final int mSubtypeNameResId;
private final int mSubtypeId;
private final String mSubtypeLocale;
+ private final String mSubtypeLanguageTag;
private final String mSubtypeMode;
private final String mSubtypeExtraValue;
private volatile HashMap<String, String> mExtraValueHashMapCache;
@@ -171,6 +175,15 @@
private String mSubtypeLocale = "";
/**
+ * @param languageTag is the BCP-47 Language Tag supported by this subtype.
+ */
+ public InputMethodSubtypeBuilder setLanguageTag(String languageTag) {
+ mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag;
+ return this;
+ }
+ private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE;
+
+ /**
* @param subtypeMode is the mode supported by this subtype.
*/
public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
@@ -271,6 +284,7 @@
mSubtypeNameResId = builder.mSubtypeNameResId;
mSubtypeIconResId = builder.mSubtypeIconResId;
mSubtypeLocale = builder.mSubtypeLocale;
+ mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
mSubtypeMode = builder.mSubtypeMode;
mSubtypeExtraValue = builder.mSubtypeExtraValue;
mIsAuxiliary = builder.mIsAuxiliary;
@@ -291,6 +305,8 @@
s = source.readString();
mSubtypeLocale = s != null ? s : "";
s = source.readString();
+ mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE;
+ s = source.readString();
mSubtypeMode = s != null ? s : "";
s = source.readString();
mSubtypeExtraValue = s != null ? s : "";
@@ -318,21 +334,38 @@
/**
* @return The locale of the subtype. This method returns the "locale" string parameter passed
* to the constructor.
+ *
+ * @deprecated Use {@link #getLanguageTag()} instead.
*/
+ @Deprecated
+ @NonNull
public String getLocale() {
return mSubtypeLocale;
}
/**
- * @return The normalized {@link Locale} object of the subtype. The returned locale may or may
- * not equal to "locale" string parameter passed to the constructor.
+ * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag
+ * is specified.
*
- * <p>TODO: Consider to make this a public API.</p>
+ * @see Locale#forLanguageTag(String)
+ */
+ @NonNull
+ public String getLanguageTag() {
+ return mSubtypeLanguageTag;
+ }
+
+ /**
+ * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+ * specified, then try to construct from {@link #getLocale()}
+ *
+ * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
* @hide
*/
@Nullable
public Locale getLocaleObject() {
- // TODO: Move the following method from InputMethodUtils to InputMethodSubtype.
+ if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+ return Locale.forLanguageTag(mSubtypeLanguageTag);
+ }
return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
}
@@ -476,13 +509,14 @@
return (subtype.hashCode() == hashCode());
}
return (subtype.hashCode() == hashCode())
- && (subtype.getLocale().equals(getLocale()))
- && (subtype.getMode().equals(getMode()))
- && (subtype.getExtraValue().equals(getExtraValue()))
- && (subtype.isAuxiliary() == isAuxiliary())
- && (subtype.overridesImplicitlyEnabledSubtype()
- == overridesImplicitlyEnabledSubtype())
- && (subtype.isAsciiCapable() == isAsciiCapable());
+ && (subtype.getLocale().equals(getLocale()))
+ && (subtype.getLanguageTag().equals(getLanguageTag()))
+ && (subtype.getMode().equals(getMode()))
+ && (subtype.getExtraValue().equals(getExtraValue()))
+ && (subtype.isAuxiliary() == isAuxiliary())
+ && (subtype.overridesImplicitlyEnabledSubtype()
+ == overridesImplicitlyEnabledSubtype())
+ && (subtype.isAsciiCapable() == isAsciiCapable());
}
return false;
}
@@ -497,6 +531,7 @@
dest.writeInt(mSubtypeNameResId);
dest.writeInt(mSubtypeIconResId);
dest.writeString(mSubtypeLocale);
+ dest.writeString(mSubtypeLanguageTag);
dest.writeString(mSubtypeMode);
dest.writeString(mSubtypeExtraValue);
dest.writeInt(mIsAuxiliary ? 1 : 0);
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index 491de78..471b6d4 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -117,6 +117,8 @@
a.getString(com.android.internal.R.styleable
.SpellChecker_Subtype_subtypeLocale),
a.getString(com.android.internal.R.styleable
+ .SpellChecker_Subtype_languageTag),
+ a.getString(com.android.internal.R.styleable
.SpellChecker_Subtype_subtypeExtraValue),
a.getInt(com.android.internal.R.styleable
.SpellChecker_Subtype_subtypeId, 0));
diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java
index f2b03cc..df33698 100644
--- a/core/java/android/view/textservice/SpellCheckerSubtype.java
+++ b/core/java/android/view/textservice/SpellCheckerSubtype.java
@@ -18,6 +18,7 @@
import com.android.internal.inputmethod.InputMethodUtils;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -40,6 +41,7 @@
* @see SpellCheckerInfo
*
* @attr ref android.R.styleable#SpellChecker_Subtype_label
+ * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag
* @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
* @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
* @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
@@ -49,11 +51,13 @@
private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
private static final int SUBTYPE_ID_NONE = 0;
+ private static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
private final int mSubtypeId;
private final int mSubtypeHashCode;
private final int mSubtypeNameResId;
private final String mSubtypeLocale;
+ private final String mSubtypeLanguageTag;
private final String mSubtypeExtraValue;
private HashMap<String, String> mExtraValueHashMapCache;
@@ -66,14 +70,17 @@
*
* @param nameId The name of the subtype
* @param locale The locale supported by the subtype
+ * @param languageTag The BCP-47 Language Tag associated with this subtype.
* @param extraValue The extra value of the subtype
* @param subtypeId The subtype ID that is supposed to be stable during package update.
*
* @hide
*/
- public SpellCheckerSubtype(int nameId, String locale, String extraValue, int subtypeId) {
+ public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue,
+ int subtypeId) {
mSubtypeNameResId = nameId;
mSubtypeLocale = locale != null ? locale : "";
+ mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE;
mSubtypeExtraValue = extraValue != null ? extraValue : "";
mSubtypeId = subtypeId;
mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
@@ -91,7 +98,7 @@
* to instantiate {@link SpellCheckerSubtype} object.
*/
public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
- this(nameId, locale, extraValue, SUBTYPE_ID_NONE);
+ this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE);
}
SpellCheckerSubtype(Parcel source) {
@@ -100,6 +107,8 @@
s = source.readString();
mSubtypeLocale = s != null ? s : "";
s = source.readString();
+ mSubtypeLanguageTag = s != null ? s : "";
+ s = source.readString();
mSubtypeExtraValue = s != null ? s : "";
mSubtypeId = source.readInt();
mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
@@ -115,12 +124,27 @@
/**
* @return the locale of the subtype
+ *
+ * @deprecated Use {@link #getLanguageTag()} instead.
*/
+ @Deprecated
+ @NonNull
public String getLocale() {
return mSubtypeLocale;
}
/**
+ * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag
+ * is specified.
+ *
+ * @see Locale#forLanguageTag(String)
+ */
+ @NonNull
+ public String getLanguageTag() {
+ return mSubtypeLanguageTag;
+ }
+
+ /**
* @return the extra value of the subtype
*/
public String getExtraValue() {
@@ -182,20 +206,24 @@
return (subtype.hashCode() == hashCode())
&& (subtype.getNameResId() == getNameResId())
&& (subtype.getLocale().equals(getLocale()))
+ && (subtype.getLanguageTag().equals(getLanguageTag()))
&& (subtype.getExtraValue().equals(getExtraValue()));
}
return false;
}
/**
- * @return The normalized {@link Locale} object of the subtype. The returned locale may or may
- * not equal to "locale" string parameter passed to the constructor.
+ * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+ * specified, then try to construct from {@link #getLocale()}
*
- * <p>TODO: Consider to make this a public API.</p>
+ * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
* @hide
*/
@Nullable
public Locale getLocaleObject() {
+ if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+ return Locale.forLanguageTag(mSubtypeLanguageTag);
+ }
return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
}
@@ -234,6 +262,7 @@
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mSubtypeNameResId);
dest.writeString(mSubtypeLocale);
+ dest.writeString(mSubtypeLanguageTag);
dest.writeString(mSubtypeExtraValue);
dest.writeInt(mSubtypeId);
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 5146bc6..2d1f855 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -75,6 +75,7 @@
import android.view.DisplayListCanvas;
import android.view.DragEvent;
import android.view.Gravity;
+import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -229,7 +230,14 @@
// Set when this TextView gained focus with some text selected. Will start selection mode.
boolean mCreatedWithASelection;
- boolean mDoubleTap = false;
+ // Indicates the current tap state (first tap, double tap, or triple click).
+ private int mTapState = TAP_STATE_INITIAL;
+ private long mLastTouchUpTime = 0;
+ private static final int TAP_STATE_INITIAL = 0;
+ private static final int TAP_STATE_FIRST_TAP = 1;
+ private static final int TAP_STATE_DOUBLE_TAP = 2;
+ // Only for mouse input.
+ private static final int TAP_STATE_TRIPLE_CLICK = 3;
private Runnable mInsertionActionModeRunnable;
@@ -769,20 +777,12 @@
return retOffset;
}
- /**
- * Adjusts selection to the word under last touch offset. Return true if the operation was
- * successfully performed.
- */
- private boolean selectCurrentWord() {
- if (!mTextView.canSelectText()) {
- return false;
- }
-
+ private boolean needsToSelectAllToSelectWordOrParagraph() {
if (mTextView.hasPasswordTransformationMethod()) {
// Always select all on a password field.
// Cut/copy menu entries are not available for passwords, but being able to select all
// is however useful to delete or paste to replace the entire content.
- return mTextView.selectAllText();
+ return true;
}
int inputType = mTextView.getInputType();
@@ -797,6 +797,21 @@
variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Adjusts selection to the word under last touch offset. Return true if the operation was
+ * successfully performed.
+ */
+ private boolean selectCurrentWord() {
+ if (!mTextView.canSelectText()) {
+ return false;
+ }
+
+ if (needsToSelectAllToSelectWordOrParagraph()) {
return mTextView.selectAllText();
}
@@ -805,8 +820,8 @@
final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets);
// Safety check in case standard touch event handling has been bypassed
- if (minOffset < 0 || minOffset >= mTextView.getText().length()) return false;
- if (maxOffset < 0 || maxOffset >= mTextView.getText().length()) return false;
+ if (minOffset < 0 || minOffset > mTextView.getText().length()) return false;
+ if (maxOffset < 0 || maxOffset > mTextView.getText().length()) return false;
int selectionStart, selectionEnd;
@@ -839,6 +854,63 @@
return selectionEnd > selectionStart;
}
+ /**
+ * Adjusts selection to the paragraph under last touch offset. Return true if the operation was
+ * successfully performed.
+ */
+ private boolean selectCurrentParagraph() {
+ if (!mTextView.canSelectText()) {
+ return false;
+ }
+
+ if (needsToSelectAllToSelectWordOrParagraph()) {
+ return mTextView.selectAllText();
+ }
+
+ long lastTouchOffsets = getLastTouchOffsets();
+ final int minLastTouchOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets);
+ final int maxLastTouchOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets);
+
+ final long paragraphsRange = getParagraphsRange(minLastTouchOffset, maxLastTouchOffset);
+ final int start = TextUtils.unpackRangeStartFromLong(paragraphsRange);
+ final int end = TextUtils.unpackRangeEndFromLong(paragraphsRange);
+ if (start < end) {
+ Selection.setSelection((Spannable) mTextView.getText(), start, end);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the minimum range of paragraphs that contains startOffset and endOffset.
+ */
+ private long getParagraphsRange(int startOffset, int endOffset) {
+ final Layout layout = mTextView.getLayout();
+ if (layout == null) {
+ return TextUtils.packRangeInLong(-1, -1);
+ }
+ final CharSequence text = mTextView.getText();
+ int minLine = layout.getLineForOffset(startOffset);
+ // Search paragraph start.
+ while (minLine > 0) {
+ final int prevLineEndOffset = layout.getLineEnd(minLine - 1);
+ if (text.charAt(prevLineEndOffset - 1) == '\n') {
+ break;
+ }
+ minLine--;
+ }
+ int maxLine = layout.getLineForOffset(endOffset);
+ // Search paragraph end.
+ while (maxLine < layout.getLineCount() - 1) {
+ final int lineEndOffset = layout.getLineEnd(maxLine);
+ if (text.charAt(lineEndOffset - 1) == '\n') {
+ break;
+ }
+ maxLine++;
+ }
+ return TextUtils.packRangeInLong(layout.getLineStart(minLine), layout.getLineEnd(maxLine));
+ }
+
void onLocaleChanged() {
// Will be re-created on demand in getWordIterator with the proper new locale
mWordIterator = null;
@@ -1219,7 +1291,31 @@
}
}
+ private void updateTapState(MotionEvent event) {
+ final int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN) {
+ final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+ // Detect double tap and triple click.
+ if (((mTapState == TAP_STATE_FIRST_TAP)
+ || ((mTapState == TAP_STATE_DOUBLE_TAP) && isMouse))
+ && (SystemClock.uptimeMillis() - mLastTouchUpTime) <=
+ ViewConfiguration.getDoubleTapTimeout()) {
+ if (mTapState == TAP_STATE_FIRST_TAP) {
+ mTapState = TAP_STATE_DOUBLE_TAP;
+ } else {
+ mTapState = TAP_STATE_TRIPLE_CLICK;
+ }
+ } else {
+ mTapState = TAP_STATE_FIRST_TAP;
+ }
+ }
+ if (action == MotionEvent.ACTION_UP) {
+ mLastTouchUpTime = SystemClock.uptimeMillis();
+ }
+ }
+
void onTouchEvent(MotionEvent event) {
+ updateTapState(event);
updateFloatingToolbarVisibility(event);
if (hasSelectionController()) {
@@ -1773,7 +1869,8 @@
stopTextActionMode();
mPreserveDetachedSelection = false;
- getSelectionController().enterDrag();
+ getSelectionController().enterDrag(
+ SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_WORD);
return true;
}
@@ -3777,30 +3874,6 @@
}
}
- public void showAtLocation(int offset) {
- // TODO - investigate if there's a better way to show the handles
- // after the drag accelerator has occured.
- int[] tmpCords = new int[2];
- mTextView.getLocationInWindow(tmpCords);
-
- Layout layout = mTextView.getLayout();
- int posX = tmpCords[0];
- int posY = tmpCords[1];
-
- final int line = layout.getLineForOffset(offset);
-
- int startX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f
- - mHotspotX - getHorizontalOffset() + getCursorOffset());
- int startY = layout.getLineBottom(line);
-
- // Take TextView's padding and scroll into account.
- startX += mTextView.viewportToContentHorizontalOffset();
- startY += mTextView.viewportToContentVerticalOffset();
-
- mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY,
- startX + posX, startY + posY);
- }
-
@Override
protected void onDraw(Canvas c) {
final int drawWidth = mDrawable.getIntrinsicWidth();
@@ -3933,13 +4006,16 @@
// Cancel the single tap delayed runnable.
if (mInsertionActionModeRunnable != null
- && (mDoubleTap || isCursorInsideEasyCorrectionSpan())) {
+ && ((mTapState == TAP_STATE_DOUBLE_TAP)
+ || (mTapState == TAP_STATE_TRIPLE_CLICK)
+ || isCursorInsideEasyCorrectionSpan())) {
mTextView.removeCallbacks(mInsertionActionModeRunnable);
}
// Prepare and schedule the single tap runnable to run exactly after the double tap
// timeout has passed.
- if (!mDoubleTap && !isCursorInsideEasyCorrectionSpan()
+ if ((mTapState != TAP_STATE_DOUBLE_TAP) && (mTapState != TAP_STATE_TRIPLE_CLICK)
+ && !isCursorInsideEasyCorrectionSpan()
&& (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)) {
if (mTextActionMode == null) {
if (mInsertionActionModeRunnable == null) {
@@ -4223,10 +4299,15 @@
boolean isExpanding;
final float xDiff = x - mPrevX;
- if (atRtl == isStartHandle()) {
- isExpanding = xDiff > 0 || currLine > mPreviousLineTouched;
+ if (isStartHandle()) {
+ isExpanding = currLine < mPreviousLineTouched;
} else {
- isExpanding = xDiff < 0 || currLine < mPreviousLineTouched;
+ isExpanding = currLine > mPreviousLineTouched;
+ }
+ if (atRtl == isStartHandle()) {
+ isExpanding |= xDiff > 0;
+ } else {
+ isExpanding |= xDiff < 0;
}
if (mTextView.getHorizontallyScrolling()) {
@@ -4492,14 +4573,24 @@
// Where the user first starts the drag motion.
private int mStartOffset = -1;
- // Indicates whether the user is selecting text and using the drag accelerator.
- private boolean mDragAcceleratorActive;
+
private boolean mHaventMovedEnoughToStartDrag;
// The line that a selection happened most recently with the drag accelerator.
private int mLineSelectionIsOn = -1;
// Whether the drag accelerator has selected past the initial line.
private boolean mSwitchedLines = false;
+ // Indicates the drag accelerator mode that the user is currently using.
+ private int mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE;
+ // Drag accelerator is inactive.
+ private static final int DRAG_ACCELERATOR_MODE_INACTIVE = 0;
+ // Character based selection by dragging. Only for mouse.
+ private static final int DRAG_ACCELERATOR_MODE_CHARACTER = 1;
+ // Word based selection by dragging. Enabled after long pressing or double tapping.
+ private static final int DRAG_ACCELERATOR_MODE_WORD = 2;
+ // Paragraph based selection by dragging. Enabled after mouse triple click.
+ private static final int DRAG_ACCELERATOR_MODE_PARAGRAPH = 3;
+
SelectionModifierCursorController() {
resetTouchOffsets();
}
@@ -4510,7 +4601,6 @@
}
initDrawables();
initHandles();
- hideInsertionPointCursorController();
}
private void initDrawables() {
@@ -4548,10 +4638,10 @@
if (mEndHandle != null) mEndHandle.hide();
}
- public void enterDrag() {
+ public void enterDrag(int dragAcceleratorMode) {
// Just need to init the handles / hide insertion cursor.
show();
- mDragAcceleratorActive = true;
+ mDragAcceleratorMode = dragAcceleratorMode;
// Start location of selection.
mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX,
mLastDownPositionY);
@@ -4563,6 +4653,7 @@
// the user to continue dragging across the screen to select text; TextView will
// scroll as necessary.
mTextView.getParent().requestDisallowInterceptTouchEvent(true);
+ mTextView.cancelLongPress();
}
public void onTouchEvent(MotionEvent event) {
@@ -4570,6 +4661,7 @@
// selection and tap can move cursor from this tap position.
final float eventX = event.getX();
final float eventY = event.getY();
+ final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (extractedTextModeWillBeStarted()) {
@@ -4582,7 +4674,8 @@
// Double tap detection
if (mGestureStayedInTapRegion) {
- if (mDoubleTap) {
+ if (mTapState == TAP_STATE_DOUBLE_TAP
+ || mTapState == TAP_STATE_TRIPLE_CLICK) {
final float deltaX = eventX - mDownPositionX;
final float deltaY = eventY - mDownPositionY;
final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
@@ -4593,8 +4686,12 @@
boolean stayedInArea =
distanceSquared < doubleTapSlop * doubleTapSlop;
- if (stayedInArea && isPositionOnText(eventX, eventY)) {
- selectCurrentWordAndStartDrag();
+ if (stayedInArea && (isMouse || isPositionOnText(eventX, eventY))) {
+ if (mTapState == TAP_STATE_DOUBLE_TAP) {
+ selectCurrentWordAndStartDrag();
+ } else if (mTapState == TAP_STATE_TRIPLE_CLICK) {
+ selectCurrentParagraphAndStartDrag();
+ }
mDiscardNextActionUp = true;
}
}
@@ -4639,94 +4736,168 @@
}
}
+ if (isMouse && !isDragAcceleratorActive()) {
+ final int offset = mTextView.getOffsetForPosition(eventX, eventY);
+ if (mStartOffset != offset) {
+ // Start character based drag accelerator.
+ if (mTextActionMode != null) {
+ mTextActionMode.finish();
+ }
+ enterDrag(DRAG_ACCELERATOR_MODE_CHARACTER);
+ mDiscardNextActionUp = true;
+ mHaventMovedEnoughToStartDrag = false;
+ }
+ }
+
if (mStartHandle != null && mStartHandle.isShowing()) {
// Don't do the drag if the handles are showing already.
break;
}
- if (mStartOffset != -1 && mTextView.getLayout() != null) {
- if (!mHaventMovedEnoughToStartDrag) {
-
- float y = eventY;
- if (mSwitchedLines) {
- // Offset the finger by the same vertical offset as the handles.
- // This improves visibility of the content being selected by
- // shifting the finger below the content, this is applied once
- // the user has switched lines.
- final float fingerOffset = (mStartHandle != null)
- ? mStartHandle.getIdealVerticalOffset()
- : touchSlop;
- y = eventY - fingerOffset;
- }
-
- final int currLine = getCurrentLineAdjustedForSlop(
- mTextView.getLayout(),
- mLineSelectionIsOn, y);
- if (!mSwitchedLines && currLine != mLineSelectionIsOn) {
- // Break early here, we want to offset the finger position from
- // the selection highlight, once the user moved their finger
- // to a different line we should apply the offset and *not* switch
- // lines until recomputing the position with the finger offset.
- mSwitchedLines = true;
- break;
- }
-
- int startOffset;
- int offset = mTextView.getOffsetAtCoordinate(currLine, eventX);
- // Snap to word boundaries.
- if (mStartOffset < offset) {
- // Expanding with end handle.
- offset = getWordEnd(offset);
- startOffset = getWordStart(mStartOffset);
- } else {
- // Expanding with start handle.
- offset = getWordStart(offset);
- startOffset = getWordEnd(mStartOffset);
- }
- mLineSelectionIsOn = currLine;
- Selection.setSelection((Spannable) mTextView.getText(),
- startOffset, offset);
- }
- }
+ updateSelection(event);
break;
case MotionEvent.ACTION_UP:
- if (mDragAcceleratorActive) {
- // No longer dragging to select text, let the parent intercept events.
- mTextView.getParent().requestDisallowInterceptTouchEvent(false);
-
- show();
- int startOffset = mTextView.getSelectionStart();
- int endOffset = mTextView.getSelectionEnd();
-
- // Since we don't let drag handles pass once they're visible, we need to
- // make sure the start / end locations are correct because the user *can*
- // switch directions during the initial drag.
- if (endOffset < startOffset) {
- int tmp = endOffset;
- endOffset = startOffset;
- startOffset = tmp;
-
- // Also update the selection with the right offsets in this case.
- Selection.setSelection((Spannable) mTextView.getText(),
- startOffset, endOffset);
- }
-
- // Need to do this to display the handles.
- mStartHandle.showAtLocation(startOffset);
- mEndHandle.showAtLocation(endOffset);
-
- // No longer the first dragging motion, reset.
- startSelectionActionMode();
-
- mDragAcceleratorActive = false;
- mStartOffset = -1;
- mSwitchedLines = false;
+ if (!isDragAcceleratorActive()) {
+ break;
}
+ updateSelection(event);
+
+ // No longer dragging to select text, let the parent intercept events.
+ mTextView.getParent().requestDisallowInterceptTouchEvent(false);
+
+ int startOffset = mTextView.getSelectionStart();
+ int endOffset = mTextView.getSelectionEnd();
+
+ // Since we don't let drag handles pass once they're visible, we need to
+ // make sure the start / end locations are correct because the user *can*
+ // switch directions during the initial drag.
+ if (endOffset < startOffset) {
+ int tmp = endOffset;
+ endOffset = startOffset;
+ startOffset = tmp;
+
+ // Also update the selection with the right offsets in this case.
+ Selection.setSelection((Spannable) mTextView.getText(),
+ startOffset, endOffset);
+ }
+ if (startOffset != endOffset) {
+ startSelectionActionMode();
+ }
+
+ // No longer the first dragging motion, reset.
+ resetDragAcceleratorState();
break;
}
}
+ private void updateSelection(MotionEvent event) {
+ if (mTextView.getLayout() != null) {
+ switch (mDragAcceleratorMode) {
+ case DRAG_ACCELERATOR_MODE_CHARACTER:
+ updateCharacterBasedSelection(event);
+ break;
+ case DRAG_ACCELERATOR_MODE_WORD:
+ updateWordBasedSelection(event);
+ break;
+ case DRAG_ACCELERATOR_MODE_PARAGRAPH:
+ updateParagraphBasedSelection(event);
+ break;
+ }
+ }
+ }
+
+ /**
+ * If the TextView allows text selection, selects the current paragraph and starts a drag.
+ *
+ * @return true if the drag was started.
+ */
+ private boolean selectCurrentParagraphAndStartDrag() {
+ if (mInsertionActionModeRunnable != null) {
+ mTextView.removeCallbacks(mInsertionActionModeRunnable);
+ }
+ if (mTextActionMode != null) {
+ mTextActionMode.finish();
+ }
+ if (!selectCurrentParagraph()) {
+ return false;
+ }
+ enterDrag(SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_PARAGRAPH);
+ return true;
+ }
+
+ private void updateCharacterBasedSelection(MotionEvent event) {
+ final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
+ Selection.setSelection((Spannable) mTextView.getText(), mStartOffset, offset);
+ }
+
+ private void updateWordBasedSelection(MotionEvent event) {
+ if (mHaventMovedEnoughToStartDrag) {
+ return;
+ }
+ final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+ final ViewConfiguration viewConfig = ViewConfiguration.get(
+ mTextView.getContext());
+ final float eventX = event.getX();
+ final float eventY = event.getY();
+ final int currLine;
+ if (isMouse) {
+ // No need to offset the y coordinate for mouse input.
+ currLine = mTextView.getLineAtCoordinate(eventY);
+ } else {
+ float y = eventY;
+ if (mSwitchedLines) {
+ // Offset the finger by the same vertical offset as the handles.
+ // This improves visibility of the content being selected by
+ // shifting the finger below the content, this is applied once
+ // the user has switched lines.
+ final int touchSlop = viewConfig.getScaledTouchSlop();
+ final float fingerOffset = (mStartHandle != null)
+ ? mStartHandle.getIdealVerticalOffset()
+ : touchSlop;
+ y = eventY - fingerOffset;
+ }
+
+ currLine = getCurrentLineAdjustedForSlop(mTextView.getLayout(), mLineSelectionIsOn,
+ y);
+ if (!mSwitchedLines && currLine != mLineSelectionIsOn) {
+ // Break early here, we want to offset the finger position from
+ // the selection highlight, once the user moved their finger
+ // to a different line we should apply the offset and *not* switch
+ // lines until recomputing the position with the finger offset.
+ mSwitchedLines = true;
+ return;
+ }
+ }
+
+ int startOffset;
+ int offset = mTextView.getOffsetAtCoordinate(currLine, eventX);
+ // Snap to word boundaries.
+ if (mStartOffset < offset) {
+ // Expanding with end handle.
+ offset = getWordEnd(offset);
+ startOffset = getWordStart(mStartOffset);
+ } else {
+ // Expanding with start handle.
+ offset = getWordStart(offset);
+ startOffset = getWordEnd(mStartOffset);
+ }
+ mLineSelectionIsOn = currLine;
+ Selection.setSelection((Spannable) mTextView.getText(),
+ startOffset, offset);
+ }
+
+ private void updateParagraphBasedSelection(MotionEvent event) {
+ final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
+
+ final int start = Math.min(offset, mStartOffset);
+ final int end = Math.max(offset, mStartOffset);
+ final long paragraphsRange = getParagraphsRange(start, end);
+ final int selectionStart = TextUtils.unpackRangeStartFromLong(paragraphsRange);
+ final int selectionEnd = TextUtils.unpackRangeEndFromLong(paragraphsRange);
+ Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
+ }
+
/**
* @param event
*/
@@ -4749,8 +4920,12 @@
public void resetTouchOffsets() {
mMinTouchOffset = mMaxTouchOffset = -1;
+ resetDragAcceleratorState();
+ }
+
+ private void resetDragAcceleratorState() {
mStartOffset = -1;
- mDragAcceleratorActive = false;
+ mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE;
mSwitchedLines = false;
}
@@ -4765,7 +4940,7 @@
* @return true if the user is selecting text using the drag accelerator.
*/
public boolean isDragAcceleratorActive() {
- return mDragAcceleratorActive;
+ return mDragAcceleratorMode != DRAG_ACCELERATOR_MODE_INACTIVE;
}
public void onTouchModeChanged(boolean isInTouchMode) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 94b75b7..5574f86 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -17,7 +17,6 @@
package android.widget;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-
import android.R;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
@@ -115,6 +114,7 @@
import android.view.DragEvent;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -618,9 +618,6 @@
private final Paint mHighlightPaint;
private boolean mHighlightPathBogus = true;
- private boolean mFirstTouch = false;
- private long mLastTouchUpTime = 0;
-
// Although these fields are specific to editable text, they are not added to Editor because
// they are defined by the TextView's style and are theme-dependent.
int mCursorDrawableRes;
@@ -8406,23 +8403,6 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
-
- if (mEditor != null && action == MotionEvent.ACTION_DOWN) {
- // Detect double tap and inform the Editor.
- if (mFirstTouch && (SystemClock.uptimeMillis() - mLastTouchUpTime) <=
- ViewConfiguration.getDoubleTapTimeout()) {
- mEditor.mDoubleTap = true;
- mFirstTouch = false;
- } else {
- mEditor.mDoubleTap = false;
- mFirstTouch = true;
- }
- }
-
- if (action == MotionEvent.ACTION_UP) {
- mLastTouchUpTime = SystemClock.uptimeMillis();
- }
-
if (mEditor != null) {
mEditor.onTouchEvent(event);
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 4a969b2..cc815c4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -7732,26 +7732,35 @@
}
final Uid u = getUidStatsLocked(mapUid(entry.uid));
- u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
- entry.rxPackets);
- u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
- entry.txPackets);
- rxPackets.put(u.getUid(), entry.rxPackets);
- txPackets.put(u.getUid(), entry.txPackets);
+ if (entry.rxBytes != 0) {
+ u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes,
+ entry.rxPackets);
+ mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+ entry.rxBytes);
+ mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
+ entry.rxPackets);
- // Sum the total number of packets so that the Rx Power and Tx Power can
- // be evenly distributed amongst the apps.
- totalRxPackets += entry.rxPackets;
- totalTxPackets += entry.txPackets;
+ rxPackets.put(u.getUid(), entry.rxPackets);
- mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
- entry.rxBytes);
- mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
- entry.txBytes);
- mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked(
- entry.rxPackets);
- mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
- entry.txPackets);
+ // Sum the total number of packets so that the Rx Power can
+ // be evenly distributed amongst the apps.
+ totalRxPackets += entry.rxPackets;
+ }
+
+ if (entry.txBytes != 0) {
+ u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes,
+ entry.txPackets);
+ mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+ entry.txBytes);
+ mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked(
+ entry.txPackets);
+
+ txPackets.put(u.getUid(), entry.txPackets);
+
+ // Sum the total number of packets so that the Tx Power can
+ // be evenly distributed amongst the apps.
+ totalTxPackets += entry.txPackets;
+ }
}
}
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 71f881e..2488111 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -595,14 +595,14 @@
MEMINFO_COUNT
};
-static long get_zram_mem_used()
+static long long get_zram_mem_used()
{
#define ZRAM_SYSFS "/sys/block/zram0/"
FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r");
if (f) {
- long mem_used_total = 0;
+ long long mem_used_total = 0;
- int matched = fscanf(f, "%*d %*d %ld %*d %*d %*d %*d", &mem_used_total);
+ int matched = fscanf(f, "%*d %*d %lld %*d %*d %*d %*d", &mem_used_total);
if (matched != 1)
ALOGW("failed to parse " ZRAM_SYSFS "mm_stat");
@@ -612,9 +612,9 @@
f = fopen(ZRAM_SYSFS "mem_used_total", "r");
if (f) {
- long mem_used_total = 0;
+ long long mem_used_total = 0;
- int matched = fscanf(f, "%ld", &mem_used_total);
+ int matched = fscanf(f, "%lld", &mem_used_total);
if (matched != 1)
ALOGW("failed to parse " ZRAM_SYSFS "mem_used_total");
diff --git a/core/res/Android.mk b/core/res/Android.mk
index cfc791d..a1bef83 100644
--- a/core/res/Android.mk
+++ b/core/res/Android.mk
@@ -23,6 +23,7 @@
# Tell aapt to create "extending (non-application)" resource IDs,
# since these resources will be used by many apps.
LOCAL_AAPT_FLAGS := -x
+LOCAL_AAPT_FLAGS += --private-symbols com.android.internal
LOCAL_MODULE_TAGS := optional
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5251b20..1127197 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -277,6 +277,7 @@
<protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
<protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" />
<protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" />
+ <protected-broadcast android:name="android.intent.action.BUGREPORT_STARTED" />
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
<protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9bca3d6..786554c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3042,7 +3042,8 @@
<attr name="imeSubtypeLocale" format="string" />
<!-- The mode of the subtype. This string can be a mode (e.g. voice, keyboard...) and this
string will be passed to the IME when the framework calls the IME with the
- subtype. -->
+ subtype. {@link android.view.inputmethod.InputMethodSubtype#getLocale()} returns the
+ value specified in this attribute. -->
<attr name="imeSubtypeMode" format="string" />
<!-- Set true if the subtype is auxiliary. An auxiliary subtype won't be shown in the
input method selection list in the settings app.
@@ -3067,6 +3068,9 @@
this subtype. This is important because many password fields only allow
ASCII-characters. -->
<attr name="isAsciiCapable" format="boolean" />
+ <!-- The BCP-47 Language Tag of the subtype. This replaces
+ {@link android.R.styleable#InputMethod_Subtype_imeSubtypeLocale}. -->
+ <attr name="languageTag" format="string" />
</declare-styleable>
<!-- Use <code>spell-checker</code> as the root tag of the XML resource that
@@ -3090,7 +3094,8 @@
<attr name="label" />
<!-- The locale of the subtype. This string should be a locale (e.g. en_US, fr_FR...)
This is also used by the framework to know the supported locales
- of the spell checker. -->
+ of the spell checker. {@link android.view.textservice.SpellCheckerSubtype#getLocale()}
+ returns the value specified in this attribute. -->
<attr name="subtypeLocale" format="string" />
<!-- The extra value of the subtype. This string can be any string and will be passed to
the SpellChecker. -->
@@ -3102,6 +3107,9 @@
{@code Arrays.hashCode(new Object[] {subtypeLocale, extraValue}) will be used instead.
-->
<attr name="subtypeId" />
+ <!-- The BCP-47 Language Tag of the subtype. This replaces
+ {@link android.R.styleable#SpellChecker_Subtype_subtypeLocale}. -->
+ <attr name="languageTag" />
</declare-styleable>
<!-- Use <code>accessibility-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b6b2e20..addeb05 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2683,6 +2683,7 @@
<public type="attr" name="encryptionAware" />
<public type="attr" name="preferenceFragmentStyle" />
<public type="attr" name="canControlMagnification" />
+ <public type="attr" name="languageTag" />
<public type="style" name="Theme.Material.DayNight" />
<public type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 177ca60..530b08d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -16,10 +16,6 @@
*/
-->
<resources>
- <!-- We don't want to publish private symbols in android.R as part of the
- SDK. Instead, put them here. -->
- <private-symbols package="com.android.internal" />
-
<!-- Private symbols that we need to reference from framework code. See
frameworks/base/core/res/MakeJavaSymbols.sed for how to easily generate
this.
@@ -2270,6 +2266,7 @@
<!-- Floating toolbar -->
<java-symbol type="id" name="floating_toolbar_menu_item_image_button" />
+ <java-symbol type="id" name="overflow" />
<java-symbol type="layout" name="floating_popup_container" />
<java-symbol type="layout" name="floating_popup_menu_button" />
<java-symbol type="layout" name="floating_popup_open_overflow_button" />
diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml
index 7ab0b13..e795c10 100644
--- a/core/tests/coretests/res/layout/activity_text_view.xml
+++ b/core/tests/coretests/res/layout/activity_text_view.xml
@@ -23,6 +23,6 @@
<EditText
android:id="@+id/textview"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="wrap_content" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
index 73fdb10..4a1c414 100644
--- a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
+++ b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
@@ -30,11 +30,16 @@
*/
public class SpellCheckerSubtypeTest extends InstrumentationTestCase {
private static final int SUBTYPE_SUBTYPE_ID_NONE = 0;
+ private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_NONE = "";
+ private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE = "";
+
private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_A = "en_GB";
+ private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_A = "en-GB";
private static final int SUBTYPE_NAME_RES_ID_A = 0x12345;
private static final String SUBTYPE_EXTRA_VALUE_A = "Key1=Value1,Key2=Value2";
private static final int SUBTYPE_SUBTYPE_ID_A = 42;
private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_B = "en_IN";
+ private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_B = "en-IN";
private static final int SUBTYPE_NAME_RES_ID_B = 0x54321;
private static final String SUBTYPE_EXTRA_VALUE_B = "Key3=Value3,Key4=Value4";
private static final int SUBTYPE_SUBTYPE_ID_B = -42;
@@ -60,9 +65,11 @@
@SmallTest
public void testSubtypeWithNoSubtypeId() throws Exception {
final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
- SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE);
+ SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE);
assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
+ assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag());
assertEquals("Value1", subtype.getExtraValueOf("Key1"));
assertEquals("Value2", subtype.getExtraValueOf("Key2"));
// Historically we have used SpellCheckerSubtype#hashCode() to track which subtype is
@@ -75,6 +82,7 @@
final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype);
assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId());
assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale());
+ assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag());
assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1"));
assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2"));
assertEquals(
@@ -84,10 +92,12 @@
public void testSubtypeWithSubtypeId() throws Exception {
final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
- SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A);
+ SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A,
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A);
assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
+ assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag());
assertEquals("Value1", subtype.getExtraValueOf("Key1"));
assertEquals("Value2", subtype.getExtraValueOf("Key2"));
// Similar to "SubtypeId" in InputMethodSubtype, "SubtypeId" in SpellCheckerSubtype enables
@@ -97,6 +107,7 @@
final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype);
assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId());
assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale());
+ assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag());
assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1"));
assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2"));
assertEquals(SUBTYPE_SUBTYPE_ID_A, clonedSubtype.hashCode());
@@ -104,30 +115,42 @@
@SmallTest
public void testGetLocaleObject() throws Exception {
- assertEquals(new Locale("en"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "en", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
- assertEquals(new Locale("en", "US"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "en_US", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
- assertEquals(new Locale("en", "US", "POSIX"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "en_US_POSIX", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
+ assertEquals(new Locale("en", "GB"),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_GB",
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
+ assertEquals(new Locale("en", "GB"),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
+ "en-GB", SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
- // Special rewrite rule for "tl" for versions of Android earlier than Lollipop that did not
- // support three letter language codes, and used "tl" (Tagalog) as the language string for
- // "fil" (Filipino).
- assertEquals(new Locale("fil"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "tl", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
- assertEquals(new Locale("fil", "PH"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "tl_PH", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
- assertEquals(new Locale("fil", "PH", "POSIX"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "tl_PH_POSIX", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
+ // If neither locale string nor language tag is specified,
+ // {@link SpellCheckerSubtype#getLocaleObject} returns null.
+ assertNull(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
- // So far rejecting invalid/unexpected locale strings is out of the scope.
- assertEquals(new Locale("a"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "a", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
- assertEquals(new Locale("a b c"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "a b c", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
- assertEquals(new Locale("en-US"), new SpellCheckerSubtype(
- SUBTYPE_NAME_RES_ID_A, "en-US", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
+ // If both locale string and language tag are specified,
+ // {@link SpellCheckerSubtype#getLocaleObject} uses language tag.
+ assertEquals(new Locale("en", "GB"),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_US", "en-GB",
+ SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
+
+ // Make sure that "tl_PH" is rewritten to "fil_PH" for spell checkers that need to support
+ // Android KitKat and prior, which do not support 3-letter language codes.
+ assertEquals(new Locale("fil", "PH"),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "tl_PH",
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
+
+ // "languageTag" attribute is available in Android N and later, where 3-letter country codes
+ // are guaranteed to be available. It's developers' responsibility for specifying a valid
+ // country subtags here and we do not rewrite "tl" to "fil" for simplicity.
+ assertEquals("tl",
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
+ "tl-PH", SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE)
+ .getLocaleObject().getLanguage());
}
@SmallTest
@@ -156,50 +179,83 @@
// If subtype ID is 0 (== SUBTYPE_SUBTYPE_ID_NONE), we keep the same behavior.
assertEquals(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE));
assertNotEqual(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE));
assertNotEqual(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE));
assertNotEqual(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_NONE));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE));
+ assertNotEqual(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_NONE),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B,
+ SUBTYPE_SUBTYPE_ID_NONE));
// If subtype ID is not 0, we test the equality based only on the subtype ID.
assertEquals(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A));
assertEquals(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A));
assertEquals(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A));
assertEquals(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_A));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A));
+ assertEquals(
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A),
+ new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B,
+ SUBTYPE_SUBTYPE_ID_A));
assertNotEqual(
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_A),
new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
- SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_B));
+ SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+ SUBTYPE_SUBTYPE_ID_B));
}
+
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index ddbdc87..83a9e01 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -16,15 +16,23 @@
package android.widget;
+import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
+import static android.widget.espresso.DragHandleUtils.onHandleView;
+import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.mouseDoubleClickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.mouseLongClickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.mouseDoubleClickAndDragOnText;
import static android.widget.espresso.TextViewActions.mouseDragOnText;
import static android.widget.espresso.TextViewActions.mouseLongClickAndDragOnText;
+import static android.widget.espresso.TextViewActions.mouseTripleClickAndDragOnText;
+import static android.widget.espresso.TextViewActions.mouseTripleClickOnTextAtIndex;
import static android.widget.espresso.TextViewAssertions.hasSelection;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import com.android.frameworks.coretests.R;
@@ -48,10 +56,23 @@
final String helloWorld = "Hello world!";
onView(withId(R.id.textview)).perform(click());
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+
+ assertNoSelectionHandles();
+
onView(withId(R.id.textview)).perform(
mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+
+ onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
+ onView(withId(R.id.textview)).check(hasSelection(""));
+
+ assertNoSelectionHandles();
}
@SmallTest
@@ -89,6 +110,9 @@
onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
helloWorld.indexOf("rld")));
onView(withId(R.id.textview)).check(hasSelection("world"));
+
+ onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(helloWorld.length()));
+ onView(withId(R.id.textview)).check(hasSelection("!"));
}
@SmallTest
@@ -113,6 +137,9 @@
onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
helloWorld.indexOf("rld")));
onView(withId(R.id.textview)).check(hasSelection("world"));
+
+ onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(helloWorld.length()));
+ onView(withId(R.id.textview)).check(hasSelection("!"));
}
@SmallTest
@@ -166,4 +193,102 @@
mouseLongClickAndDragOnText(text.indexOf("j"), text.indexOf("f")));
onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
}
+
+ @SmallTest
+ public void testSelectTextByTripleClick() throws Exception {
+ getActivity();
+
+ final StringBuilder builder = new StringBuilder();
+ builder.append("First paragraph.\n");
+ builder.append("Second paragraph.");
+ for (int i = 0; i < 10; i++) {
+ builder.append(" This paragraph is very long.");
+ }
+ builder.append('\n');
+ builder.append("Third paragraph.");
+ final String text = builder.toString();
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickOnTextAtIndex(text.indexOf("rst")));
+ onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n"));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickOnTextAtIndex(text.indexOf("cond")));
+ onView(withId(R.id.textview)).check(hasSelection(
+ text.substring(text.indexOf("Second"), text.indexOf("Third"))));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickOnTextAtIndex(text.indexOf("ird")));
+ onView(withId(R.id.textview)).check(hasSelection("Third paragraph."));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickOnTextAtIndex(text.indexOf("very long")));
+ onView(withId(R.id.textview)).check(hasSelection(
+ text.substring(text.indexOf("Second"), text.indexOf("Third"))));
+ }
+
+ @SmallTest
+ public void testSelectTextByTripleClickAndDrag() throws Exception {
+ getActivity();
+
+ final StringBuilder builder = new StringBuilder();
+ builder.append("First paragraph.\n");
+ builder.append("Second paragraph.");
+ for (int i = 0; i < 10; i++) {
+ builder.append(" This paragraph is very long.");
+ }
+ builder.append('\n');
+ builder.append("Third paragraph.");
+ final String text = builder.toString();
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickAndDragOnText(text.indexOf("irst"), text.indexOf("st")));
+ onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n"));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickAndDragOnText(text.indexOf("cond"), text.indexOf("Third") - 2));
+ onView(withId(R.id.textview)).check(hasSelection(
+ text.substring(text.indexOf("Second"), text.indexOf("Third"))));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickAndDragOnText(text.indexOf("First"), text.indexOf("ird")));
+ onView(withId(R.id.textview)).check(hasSelection(text));
+ }
+
+ @SmallTest
+ public void testSelectTextByTripleClickAndDrag_reverse() throws Exception {
+ getActivity();
+
+ final StringBuilder builder = new StringBuilder();
+ builder.append("First paragraph.\n");
+ builder.append("Second paragraph.");
+ for (int i = 0; i < 10; i++) {
+ builder.append(" This paragraph is very long.");
+ }
+ builder.append('\n');
+ builder.append("Third paragraph.");
+ final String text = builder.toString();
+
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickAndDragOnText(text.indexOf("st"), text.indexOf("irst")));
+ onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n"));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickAndDragOnText(text.indexOf("Third") - 2, text.indexOf("cond")));
+ onView(withId(R.id.textview)).check(hasSelection(
+ text.substring(text.indexOf("Second"), text.indexOf("Third"))));
+
+ onView(withId(R.id.textview)).perform(
+ mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First")));
+ onView(withId(R.id.textview)).check(hasSelection(text));
+ }
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 4614505..e54723e 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
+import static android.widget.espresso.DragHandleUtils.onHandleView;
import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
@@ -26,24 +28,22 @@
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
import static android.widget.espresso.TextViewAssertions.hasSelection;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.pressKey;
+import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
-import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
-import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
-import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
-import static org.hamcrest.Matchers.allOf;
import com.android.frameworks.coretests.R;
-import android.support.test.espresso.NoMatchingRootException;
-import android.support.test.espresso.NoMatchingViewException;
-import android.support.test.espresso.ViewInteraction;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
@@ -153,19 +153,115 @@
@SmallTest
public void testToolbarAppearsAfterSelection() throws Exception {
- // It'll be nice to check that the toolbar is not visible (or does not exist) here
- // I can't currently find a way to do this. I'll get to it later.
-
final String text = "Toolbar appears after selection.";
onView(withId(R.id.textview)).perform(click());
+ assertFloatingToolbarIsNotDisplayed();
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
onView(withId(R.id.textview)).perform(
longPressOnTextAtIndex(text.indexOf("appears")));
- // It takes the toolbar less than 100ms to start to animate into screen.
- // Ideally, we'll wait using the UiController, but I guess this works for now.
- Thread.sleep(100);
- assertFloatingToolbarIsDisplayed(getActivity());
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+
+ final String text2 = "Toolbar disappears after typing text.";
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text2));
+ assertFloatingToolbarIsNotDisplayed();
+ }
+
+ @SmallTest
+ public void testToolbarAndInsertionHandle() throws Exception {
+ final String text = "text";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+ assertFloatingToolbarIsNotDisplayed();
+
+ onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+
+ assertFloatingToolbarContainsItem(
+ getActivity().getString(com.android.internal.R.string.selectAll));
+ assertFloatingToolbarDoesNotContainItem(
+ getActivity().getString(com.android.internal.R.string.copy));
+ assertFloatingToolbarDoesNotContainItem(
+ getActivity().getString(com.android.internal.R.string.cut));
+ }
+
+ @SmallTest
+ public void testToolbarAndSelectionHandle() throws Exception {
+ final String text = "abcd efg hijk";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+
+ assertFloatingToolbarContainsItem(
+ getActivity().getString(com.android.internal.R.string.selectAll));
+ assertFloatingToolbarContainsItem(
+ getActivity().getString(com.android.internal.R.string.copy));
+ assertFloatingToolbarContainsItem(
+ getActivity().getString(com.android.internal.R.string.cut));
+
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
+ sleepForFloatingToolbarPopup();
+ assertFloatingToolbarIsDisplayed();
+
+ assertFloatingToolbarDoesNotContainItem(
+ getActivity().getString(com.android.internal.R.string.selectAll));
+ assertFloatingToolbarContainsItem(
+ getActivity().getString(com.android.internal.R.string.copy));
+ assertFloatingToolbarContainsItem(
+ getActivity().getString(com.android.internal.R.string.cut));
+ }
+
+ @SmallTest
+ public void testInsertionHandle() throws Exception {
+ final String text = "abcd efg hijk ";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
+
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+
+ onHandleView(com.android.internal.R.id.insertion_handle)
+ .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
+
+ onHandleView(com.android.internal.R.id.insertion_handle)
+ .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
+ }
+
+ @SmallTest
+ public void testInsertionHandle_multiLine() throws Exception {
+ final String text = "abcd\n" + "efg\n" + "hijk\n";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
+
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+
+ onHandleView(com.android.internal.R.id.insertion_handle)
+ .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
+
+ onHandleView(com.android.internal.R.id.insertion_handle)
+ .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
}
@SmallTest
@@ -183,7 +279,7 @@
onHandleView(com.android.internal.R.id.selection_end_handle)
.check(matches(isDisplayed()));
- final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
onView(withId(R.id.textview)).check(hasSelection("abcd efg"));
@@ -200,7 +296,7 @@
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i')));
- final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('e')));
onView(withId(R.id.textview)).check(hasSelection("efg\nhijk"));
@@ -219,13 +315,46 @@
}
@SmallTest
+ public void testSelectionHandles_multiLine_rtl() throws Exception {
+ // Arabic text.
+ final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
+ + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n"
+ + "\u0639\u063A\u063B";
+ onView(withId(R.id.textview)).perform(click());
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+ onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('\u0634')));
+
+ final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062E')));
+ onView(withId(R.id.textview)).check(hasSelection(
+ text.substring(text.indexOf('\u062D'), text.indexOf('\u0635') + 1)));
+
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062A')));
+ onView(withId(R.id.textview)).check(hasSelection(
+ text.substring(text.indexOf('\u062A'), text.indexOf('\u0635') + 1)));
+
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u0638')));
+ onView(withId(R.id.textview)).check(hasSelection(
+ text.substring(text.indexOf('\u062A'), text.indexOf('\u0638') + 1)));
+
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u063B')));
+ onView(withId(R.id.textview)).check(hasSelection(text));
+ }
+
+
+ @SmallTest
public void testSelectionHandles_doesNotPassAnotherHandle() throws Exception {
final String text = "abcd efg hijk lmn";
onView(withId(R.id.textview)).perform(click());
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('f')));
- final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('l')));
onView(withId(R.id.textview)).check(hasSelection("g"));
@@ -243,7 +372,7 @@
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i')));
- final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('r') + 1));
onView(withId(R.id.textview)).check(hasSelection("k"));
@@ -261,7 +390,7 @@
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i')));
- final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
@@ -314,7 +443,7 @@
onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('m')));
- final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+ final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
onHandleView(com.android.internal.R.id.selection_start_handle)
.perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
@@ -342,25 +471,4 @@
.perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
onView(withId(R.id.textview)).check(hasSelection("hijk"));
}
-
- private static void assertNoSelectionHandles() {
- try {
- onHandleView(com.android.internal.R.id.selection_start_handle)
- .check(matches(isDisplayed()));
- } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
- try {
- onHandleView(com.android.internal.R.id.selection_end_handle)
- .check(matches(isDisplayed()));
- } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) {
- return;
- }
- }
- throw new AssertionError("Selection handle found");
- }
-
- private static ViewInteraction onHandleView(int id)
- throws NoMatchingRootException, NoMatchingViewException, AssertionError {
- return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
- .inRoot(withDecorView(hasDescendant(withId(id))));
- }
}
diff --git a/core/tests/coretests/src/android/widget/espresso/DragAction.java b/core/tests/coretests/src/android/widget/espresso/DragAction.java
index ce97568..b2c8e38 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragAction.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragAction.java
@@ -157,6 +157,59 @@
},
/**
+ * Starts a drag with a mouse triple click.
+ */
+ MOUSE_TRIPLE_CLICK {
+ private DownMotionPerformer downMotion = new DownMotionPerformer() {
+ @Override
+ @Nullable
+ public MotionEvent perform(
+ UiController uiController, float[] coordinates, float[] precision) {
+ MotionEvent downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision)
+ .down;
+ for (int i = 0; i < 2; ++i) {
+ try {
+ if (!MotionEvents.sendUp(uiController, downEvent)) {
+ String logMessage = "Injection of up event as part of the triple "
+ + "click failed. Sending cancel event.";
+ Log.d(TAG, logMessage);
+ MotionEvents.sendCancel(uiController, downEvent);
+ return null;
+ }
+
+ long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
+ uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
+ } finally {
+ downEvent.recycle();
+ }
+ downEvent = MotionEvents.sendDown(
+ uiController, coordinates, precision).down;
+ }
+ return downEvent;
+ }
+ };
+
+ @Override
+ public Status sendSwipe(
+ UiController uiController,
+ float[] startCoordinates, float[] endCoordinates, float[] precision) {
+ return sendLinearDrag(
+ uiController, downMotion, startCoordinates, endCoordinates, precision);
+ }
+
+ @Override
+ public String toString() {
+ return "mouse triple click and drag to select";
+ }
+
+ @Override
+ public UiController wrapUiController(UiController uiController) {
+ return new MouseUiController(uiController);
+ }
+ },
+
+ /**
* Starts a drag with a tap.
*/
TAP {
diff --git a/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
new file mode 100644
index 0000000..f744cae
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.widget.espresso;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.Matchers.allOf;
+
+import android.support.test.espresso.NoMatchingRootException;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.ViewInteraction;
+import android.widget.Editor;
+
+public class DragHandleUtils {
+ private DragHandleUtils() {
+
+ }
+
+ public static void assertNoSelectionHandles() {
+ try {
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+ try {
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) {
+ return;
+ }
+ }
+ throw new AssertionError("Selection handle found");
+ }
+
+ public static ViewInteraction onHandleView(int id)
+ throws NoMatchingRootException, NoMatchingViewException, AssertionError {
+ return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
+ .inRoot(withDecorView(hasDescendant(withId(id))));
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
index fc01d84..f02fe00 100644
--- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
@@ -19,31 +19,133 @@
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static android.support.test.espresso.matcher.ViewMatchers.withTagValue;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import android.app.Activity;
+import org.hamcrest.Matcher;
+
+import android.support.test.espresso.NoMatchingRootException;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.ViewInteraction;
+import android.support.test.espresso.action.ViewActions;
+import android.support.test.espresso.matcher.ViewMatchers;
+import android.view.View;
+
import com.android.internal.widget.FloatingToolbar;
/**
* Espresso utility methods for the floating toolbar.
*/
public class FloatingToolbarEspressoUtils {
-
+ private final static Object TAG = FloatingToolbar.FLOATING_TOOLBAR_TAG;
private FloatingToolbarEspressoUtils() {}
+ private static ViewInteraction onFloatingToolBar() {
+ return onView(withTagValue(is(TAG)))
+ .inRoot(withDecorView(hasDescendant(withTagValue(is(TAG)))));
+ }
+
/**
* Asserts that the floating toolbar is displayed on screen.
*
* @throws AssertionError if the assertion fails
*/
- public static void assertFloatingToolbarIsDisplayed(Activity activity) {
- onView(withTagValue(is((Object) FloatingToolbar.FLOATING_TOOLBAR_TAG)))
- .inRoot(withDecorView(not(is(activity.getWindow().getDecorView()))))
- .check(matches(isDisplayed()));
+ public static void assertFloatingToolbarIsDisplayed() {
+ onFloatingToolBar().check(matches(isDisplayed()));
}
+ /**
+ * Asserts that the floating toolbar is not displayed on screen.
+ *
+ * @throws AssertionError if the assertion fails
+ */
+ public static void assertFloatingToolbarIsNotDisplayed() {
+ try {
+ onFloatingToolBar().check(matches(isDisplayed()));
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+ return;
+ }
+ throw new AssertionError("Floating toolbar is displayed");
+ }
+
+ private static void toggleOverflow() {
+ final int id = com.android.internal.R.id.overflow;
+ onView(allOf(withId(id), isDisplayed()))
+ .inRoot(withDecorView(hasDescendant(withId(id))))
+ .perform(ViewActions.click());
+ onView(isRoot()).perform(SLEEP);
+ }
+
+ public static void sleepForFloatingToolbarPopup() {
+ onView(isRoot()).perform(SLEEP);
+ }
+
+ /**
+ * Asserts that the floating toolbar contains the specified item.
+ *
+ * @param itemLabel label of the item.
+ * @throws AssertionError if the assertion fails
+ */
+ public static void assertFloatingToolbarContainsItem(String itemLabel) {
+ try{
+ onFloatingToolBar().check(matches(hasDescendant(ViewMatchers.withText(itemLabel))));
+ } catch (AssertionError e) {
+ try{
+ toggleOverflow();
+ } catch (NoMatchingViewException | NoMatchingRootException e2) {
+ // No overflow items.
+ throw e;
+ }
+ try{
+ onFloatingToolBar().check(matches(hasDescendant(ViewMatchers.withText(itemLabel))));
+ } finally {
+ toggleOverflow();
+ }
+ }
+ }
+
+ /**
+ * Asserts that the floating toolbar doesn't contain the specified item.
+ *
+ * @param itemLabel label of the item.
+ * @throws AssertionError if the assertion fails
+ */
+ public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) {
+ try{
+ assertFloatingToolbarContainsItem(itemLabel);
+ } catch (AssertionError e) {
+ return;
+ }
+ throw new AssertionError("Floating toolbar contains " + itemLabel);
+ }
+
+ /**
+ * ViewAction to sleep to wait floating toolbar's animation.
+ */
+ private static final ViewAction SLEEP = new ViewAction() {
+ private static final long SLEEP_DURATION = 400;
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sleep " + SLEEP_DURATION + " ms.";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadForAtLeast(SLEEP_DURATION);
+ }
+ };
}
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
index de640ca..e51f2785 100644
--- a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
+++ b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
@@ -22,9 +22,13 @@
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.CoordinatesProvider;
import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.MotionEvents;
+import android.support.test.espresso.action.MotionEvents.DownResultHolder;
import android.support.test.espresso.action.PrecisionDescriber;
+import android.support.test.espresso.action.Press;
import android.support.test.espresso.action.Tapper;
import android.view.View;
+import android.view.ViewConfiguration;
/**
* ViewAction for performing an click on View by a mouse.
@@ -32,10 +36,58 @@
public final class MouseClickAction implements ViewAction {
private final GeneralClickAction mGeneralClickAction;
- public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
- PrecisionDescriber precisionDescriber) {
+ public enum CLICK implements Tapper {
+ TRIPLE {
+ @Override
+ public Tapper.Status sendTap(UiController uiController, float[] coordinates,
+ float[] precision) {
+ Tapper.Status stat = sendSingleTap(uiController, coordinates, precision);
+ boolean warning = false;
+ if (stat == Tapper.Status.FAILURE) {
+ return Tapper.Status.FAILURE;
+ } else if (stat == Tapper.Status.WARNING) {
+ warning = true;
+ }
+
+ long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
+ for (int i = 0; i < 2; i++) {
+ if (0 < doubleTapMinimumTimeout) {
+ uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
+ }
+ stat = sendSingleTap(uiController, coordinates, precision);
+ if (stat == Tapper.Status.FAILURE) {
+ return Tapper.Status.FAILURE;
+ } else if (stat == Tapper.Status.WARNING) {
+ warning = true;
+ }
+ }
+
+ if (warning) {
+ return Tapper.Status.WARNING;
+ } else {
+ return Tapper.Status.SUCCESS;
+ }
+ }
+ };
+
+ private static Tapper.Status sendSingleTap(UiController uiController,
+ float[] coordinates, float[] precision) {
+ DownResultHolder res = MotionEvents.sendDown(uiController, coordinates, precision);
+ try {
+ if (!MotionEvents.sendUp(uiController, res.down)) {
+ MotionEvents.sendCancel(uiController, res.down);
+ return Tapper.Status.FAILURE;
+ }
+ } finally {
+ res.down.recycle();
+ }
+ return res.longPress ? Tapper.Status.WARNING : Tapper.Status.SUCCESS;
+ }
+ };
+
+ public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider) {
mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider,
- precisionDescriber);
+ Press.PINPOINT);
}
@Override
@@ -51,5 +103,13 @@
@Override
public void perform(UiController uiController, View view) {
mGeneralClickAction.perform(new MouseUiController(uiController), view);
+ long doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+ if (0 < doubleTapTimeout) {
+ // Wait to avoid false gesture detection. Without this wait, consecutive clicks can be
+ // detected as a triple click. e.g. 2 double clicks are detected as a triple click and
+ // a single click because espresso isn't aware of triple click detection logic, which
+ // is TextView specific gesture.
+ uiController.loopMainThreadForAtLeast(doubleTapTimeout);
+ }
}
}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 32cc6d6..54d5823 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -64,7 +64,7 @@
*/
public static ViewAction mouseClickOnTextAtIndex(int index) {
return actionWithAssertions(
- new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), Press.PINPOINT));
+ new MouseClickAction(Tap.SINGLE, new TextCoordinates(index)));
}
/**
@@ -94,7 +94,7 @@
*/
public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
return actionWithAssertions(
- new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.PINPOINT));
+ new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index)));
}
/**
@@ -124,7 +124,22 @@
*/
public static ViewAction mouseLongClickOnTextAtIndex(int index) {
return actionWithAssertions(
- new MouseClickAction(Tap.LONG, new TextCoordinates(index), Press.PINPOINT));
+ new MouseClickAction(Tap.LONG, new TextCoordinates(index)));
+ }
+
+ /**
+ * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param index The index of the TextView's text to triple-click on.
+ */
+ public static ViewAction mouseTripleClickOnTextAtIndex(int index) {
+ return actionWithAssertions(
+ new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index)));
}
/**
@@ -237,6 +252,28 @@
TextView.class));
}
+ /**
+ * Returns an action that triple click then drags by mouse on text from startIndex to endIndex
+ * on the TextView.<br>
+ * <br>
+ * View constraints:
+ * <ul>
+ * <li>must be a TextView displayed on screen
+ * <ul>
+ *
+ * @param startIndex The index of the TextView's text to start a drag from
+ * @param endIndex The index of the TextView's text to end the drag at
+ */
+ public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) {
+ return actionWithAssertions(
+ new DragAction(
+ DragAction.Drag.MOUSE_TRIPLE_CLICK,
+ new TextCoordinates(startIndex),
+ new TextCoordinates(endIndex),
+ Press.PINPOINT,
+ TextView.class));
+ }
+
public enum Handle {
SELECTION_START,
SELECTION_END,
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index f1c89b8..d4dbb00 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -309,6 +309,14 @@
LOG_ALWAYS_FATAL("unsupported operation");
}
+void BakedOpDispatcher::onCirclePropsOp(BakedOpRenderer&, const CirclePropsOp&, const BakedOpState&) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpDispatcher::onRoundRectPropsOp(BakedOpRenderer&, const RoundRectPropsOp&, const BakedOpState&) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
namespace VertexBufferRenderFlags {
enum {
Offset = 0x1,
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index b936e6d5..ec03e83 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -467,13 +467,13 @@
// (temp layers are clipped to viewport, since they don't persist offscreen content)
SkPaint saveLayerPaint;
saveLayerPaint.setAlpha(properties.getAlpha());
- onBeginLayerOp(*new (mAllocator) BeginLayerOp(
+ deferBeginLayerOp(*new (mAllocator) BeginLayerOp(
saveLayerBounds,
Matrix4::identity(),
saveLayerBounds,
&saveLayerPaint));
deferNodeOps(node);
- onEndLayerOp(*new (mAllocator) EndLayerOp());
+ deferEndLayerOp(*new (mAllocator) EndLayerOp());
} else {
deferNodeOps(node);
}
@@ -559,7 +559,7 @@
}
const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
- deferRenderNodeOp(*childOp);
+ deferRenderNodeOpImpl(*childOp);
drawIndex++;
}
}
@@ -645,7 +645,7 @@
int restoreTo = mCanvasState.save(SkCanvas::kMatrix_SaveFlag);
mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
- deferRenderNodeOp(*childOp);
+ deferRenderNodeOpImpl(*childOp);
mCanvasState.restoreToCount(restoreTo);
}
@@ -653,13 +653,13 @@
}
/**
- * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
+ * Used to define a list of lambdas referencing private OpReorderer::onXX::defer() methods.
*
* This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
* E.g. a BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
*/
#define OP_RECEIVER(Type) \
- [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
+ [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.defer##Type(static_cast<const Type&>(op)); },
void OpReorderer::deferNodeOps(const RenderNode& renderNode) {
typedef void (*OpDispatcher) (OpReorderer& reorderer, const RecordedOp& op);
static OpDispatcher receivers[] = {
@@ -687,7 +687,7 @@
}
}
-void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
+void OpReorderer::deferRenderNodeOpImpl(const RenderNodeOp& op) {
if (op.renderNode->nothingToDraw()) return;
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
@@ -702,9 +702,9 @@
mCanvasState.restoreToCount(count);
}
-void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
+void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
if (!op.skipInOrderDraw) {
- deferRenderNodeOp(op);
+ deferRenderNodeOpImpl(op);
}
}
@@ -712,7 +712,7 @@
* Defers an unmergeable, strokeable op, accounting correctly
* for paint's style on the bounds being computed.
*/
-void OpReorderer::onStrokeableOp(const RecordedOp& op, batchid_t batchId,
+void OpReorderer::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior) {
// Note: here we account for stroke when baking the op
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
@@ -734,11 +734,11 @@
: (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
}
-void OpReorderer::onArcOp(const ArcOp& op) {
- onStrokeableOp(op, tessBatchId(op));
+void OpReorderer::deferArcOp(const ArcOp& op) {
+ deferStrokeableOp(op, tessBatchId(op));
}
-void OpReorderer::onBitmapOp(const BitmapOp& op) {
+void OpReorderer::deferBitmapOp(const BitmapOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
@@ -757,28 +757,43 @@
}
}
-void OpReorderer::onBitmapMeshOp(const BitmapMeshOp& op) {
+void OpReorderer::deferBitmapMeshOp(const BitmapMeshOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
}
-void OpReorderer::onBitmapRectOp(const BitmapRectOp& op) {
+void OpReorderer::deferBitmapRectOp(const BitmapRectOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
}
-void OpReorderer::onLinesOp(const LinesOp& op) {
+void OpReorderer::deferCirclePropsOp(const CirclePropsOp& op) {
+ // allocate a temporary oval op (with mAllocator, so it persists until render), so the
+ // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
+ float x = *(op.x);
+ float y = *(op.y);
+ float radius = *(op.radius);
+ Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius);
+ const OvalOp* resolvedOp = new (mAllocator) OvalOp(
+ unmappedBounds,
+ op.localMatrix,
+ op.localClipRect,
+ op.paint);
+ deferOvalOp(*resolvedOp);
+}
+
+void OpReorderer::deferLinesOp(const LinesOp& op) {
batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
- onStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
+ deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
}
-void OpReorderer::onOvalOp(const OvalOp& op) {
- onStrokeableOp(op, tessBatchId(op));
+void OpReorderer::deferOvalOp(const OvalOp& op) {
+ deferStrokeableOp(op, tessBatchId(op));
}
-void OpReorderer::onPatchOp(const PatchOp& op) {
+void OpReorderer::deferPatchOp(const PatchOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
@@ -795,30 +810,41 @@
}
}
-void OpReorderer::onPathOp(const PathOp& op) {
- onStrokeableOp(op, OpBatchType::Bitmap);
+void OpReorderer::deferPathOp(const PathOp& op) {
+ deferStrokeableOp(op, OpBatchType::Bitmap);
}
-void OpReorderer::onPointsOp(const PointsOp& op) {
+void OpReorderer::deferPointsOp(const PointsOp& op) {
batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
- onStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
+ deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
}
-void OpReorderer::onRectOp(const RectOp& op) {
- onStrokeableOp(op, tessBatchId(op));
+void OpReorderer::deferRectOp(const RectOp& op) {
+ deferStrokeableOp(op, tessBatchId(op));
}
-void OpReorderer::onRoundRectOp(const RoundRectOp& op) {
- onStrokeableOp(op, tessBatchId(op));
+void OpReorderer::deferRoundRectOp(const RoundRectOp& op) {
+ deferStrokeableOp(op, tessBatchId(op));
}
-void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
+void OpReorderer::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
+ // allocate a temporary round rect op (with mAllocator, so it persists until render), so the
+ // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
+ const RoundRectOp* resolvedOp = new (mAllocator) RoundRectOp(
+ Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)),
+ op.localMatrix,
+ op.localClipRect,
+ op.paint, *op.rx, *op.ry);
+ deferRoundRectOp(*resolvedOp);
+}
+
+void OpReorderer::deferSimpleRectsOp(const SimpleRectsOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
}
-void OpReorderer::onTextOp(const TextOp& op) {
+void OpReorderer::deferTextOp(const TextOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
@@ -861,7 +887,7 @@
}
// TODO: test rejection at defer time, where the bounds become empty
-void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+void OpReorderer::deferBeginLayerOp(const BeginLayerOp& op) {
uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
@@ -906,7 +932,7 @@
&op, nullptr);
}
-void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
+void OpReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) {
const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
int finishedLayerIndex = mLayerStack.back();
@@ -932,11 +958,11 @@
}
}
-void OpReorderer::onLayerOp(const LayerOp& op) {
+void OpReorderer::deferLayerOp(const LayerOp& op) {
LOG_ALWAYS_FATAL("unsupported");
}
-void OpReorderer::onShadowOp(const ShadowOp& op) {
+void OpReorderer::deferShadowOp(const ShadowOp& op) {
LOG_ALWAYS_FATAL("unsupported");
}
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 0b88f04..35343c8b 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -237,7 +237,7 @@
void deferNodeOps(const RenderNode& renderNode);
- void deferRenderNodeOp(const RenderNodeOp& op);
+ void deferRenderNodeOpImpl(const RenderNodeOp& op);
void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
@@ -246,17 +246,17 @@
return mFrameAllocatedPaths.back().get();
}
- void onStrokeableOp(const RecordedOp& op, batchid_t batchId,
+ void deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
/**
- * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type.
+ * Declares all OpReorderer::deferXXXXOp() methods for every RecordedOp type.
*
* These private methods are called from within deferImpl to defer each individual op
* type differently.
*/
#define INTERNAL_OP_HANDLER(Type) \
- void on##Type(const Type& op);
+ void defer##Type(const Type& op);
MAP_OPS(INTERNAL_OP_HANDLER)
std::vector<std::unique_ptr<SkPath> > mFrameAllocatedPaths;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 75ecdae..d1a4866 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -48,6 +48,7 @@
M_OP_FN(BitmapOp) \
U_OP_FN(BitmapMeshOp) \
U_OP_FN(BitmapRectOp) \
+ U_OP_FN(CirclePropsOp) \
U_OP_FN(LinesOp) \
U_OP_FN(OvalOp) \
M_OP_FN(PatchOp) \
@@ -56,6 +57,7 @@
U_OP_FN(RectOp) \
U_OP_FN(RenderNodeOp) \
U_OP_FN(RoundRectOp) \
+ U_OP_FN(RoundRectPropsOp) \
U_OP_FN(ShadowOp) \
U_OP_FN(SimpleRectsOp) \
M_OP_FN(TextOp) \
@@ -181,6 +183,18 @@
const Rect src;
};
+struct CirclePropsOp : RecordedOp {
+ CirclePropsOp(const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint,
+ float* x, float* y, float* radius)
+ : RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClipRect, paint)
+ , x(x)
+ , y(y)
+ , radius(radius) {}
+ const float* x;
+ const float* y;
+ const float* radius;
+};
+
struct LinesOp : RecordedOp {
LinesOp(BASE_PARAMS, const float* points, const int floatCount)
: SUPER(LinesOp)
@@ -195,7 +209,6 @@
: SUPER(OvalOp) {}
};
-
struct PatchOp : RecordedOp {
PatchOp(BASE_PARAMS, const SkBitmap* bitmap, const Res_png_9patch* patch)
: SUPER(PatchOp)
@@ -235,6 +248,24 @@
const float ry;
};
+struct RoundRectPropsOp : RecordedOp {
+ RoundRectPropsOp(const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint,
+ float* left, float* top, float* right, float* bottom, float *rx, float *ry)
+ : RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClipRect, paint)
+ , left(left)
+ , top(top)
+ , right(right)
+ , bottom(bottom)
+ , rx(rx)
+ , ry(ry) {}
+ const float* left;
+ const float* top;
+ const float* right;
+ const float* bottom;
+ const float* rx;
+ const float* ry;
+};
+
/**
* Real-time, dynamic-lit shadow.
*
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 57f0d34..1bf92be 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -343,11 +343,49 @@
refPaint(&paint), rx, ry));
}
+void RecordingCanvas::drawRoundRect(
+ CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top,
+ CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom,
+ CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry,
+ CanvasPropertyPaint* paint) {
+ mDisplayList->ref(left);
+ mDisplayList->ref(top);
+ mDisplayList->ref(right);
+ mDisplayList->ref(bottom);
+ mDisplayList->ref(rx);
+ mDisplayList->ref(ry);
+ mDisplayList->ref(paint);
+ refBitmapsInShader(paint->value.getShader());
+ addOp(new (alloc()) RoundRectPropsOp(
+ *(mState.currentSnapshot()->transform),
+ mState.getRenderTargetClipBounds(),
+ &paint->value,
+ &left->value, &top->value, &right->value, &bottom->value,
+ &rx->value, &ry->value));
+}
+
void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
+ // TODO: move to Canvas.h
if (radius <= 0) return;
drawOval(x - radius, y - radius, x + radius, y + radius, paint);
}
+void RecordingCanvas::drawCircle(
+ CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
+ CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) {
+ mDisplayList->ref(x);
+ mDisplayList->ref(y);
+ mDisplayList->ref(radius);
+ mDisplayList->ref(paint);
+ refBitmapsInShader(paint->value.getShader());
+ addOp(new (alloc()) CirclePropsOp(
+ *(mState.currentSnapshot()->transform),
+ mState.getRenderTargetClipBounds(),
+ &paint->value,
+ &x->value, &y->value, &radius->value));
+}
+
+
void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
addOp(new (alloc()) OvalOp(
Rect(left, top, right, bottom),
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 16a2771..6fbaa8a 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -69,6 +69,17 @@
virtual GLuint getTargetFbo() const override { return -1; }
// ----------------------------------------------------------------------------
+// HWUI Canvas draw operations
+// ----------------------------------------------------------------------------
+
+ void drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top,
+ CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom,
+ CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry,
+ CanvasPropertyPaint* paint);
+ void drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
+ CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint);
+
+// ----------------------------------------------------------------------------
// android/graphics/Canvas interface
// ----------------------------------------------------------------------------
virtual SkCanvas* asSkCanvas() override;
diff --git a/libs/hwui/tests/common/scenes/OpPropAnimation.cpp b/libs/hwui/tests/common/scenes/OpPropAnimation.cpp
new file mode 100644
index 0000000..5dfb2b4
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/OpPropAnimation.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+#include "utils/Color.h"
+
+class OpPropAnimation;
+
+static TestScene::Registrar _Shapes(TestScene::Info{
+ "opprops",
+ "A minimal demonstration of CanvasProperty drawing operations.",
+ TestScene::simpleCreateScene<OpPropAnimation>
+});
+
+class OpPropAnimation : public TestScene {
+public:
+ sp<CanvasPropertyPaint> mPaint = new CanvasPropertyPaint(SkPaint());
+
+ sp<CanvasPropertyPrimitive> mRoundRectLeft = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectTop = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectRight = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectBottom = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectRx = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mRoundRectRy = new CanvasPropertyPrimitive(0);
+
+ sp<CanvasPropertyPrimitive> mCircleX = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mCircleY = new CanvasPropertyPrimitive(0);
+ sp<CanvasPropertyPrimitive> mCircleRadius = new CanvasPropertyPrimitive(0);
+
+ sp<RenderNode> content;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ content = TestUtils::createNode(0, 0, width, height,
+ [this, width, height](RenderProperties& props, TestCanvas& canvas) {
+ mPaint->value.setAntiAlias(true);
+ mPaint->value.setColor(Color::Blue_500);
+
+ mRoundRectRight->value = width / 2;
+ mRoundRectBottom->value = height / 2;
+
+ mCircleX->value = width * 0.75;
+ mCircleY->value = height * 0.75;
+
+ canvas.drawColor(Color::White, SkXfermode::Mode::kSrcOver_Mode);
+ canvas.drawRoundRect(mRoundRectLeft.get(), mRoundRectTop.get(),
+ mRoundRectRight.get(), mRoundRectBottom.get(),
+ mRoundRectRx.get(), mRoundRectRy.get(), mPaint.get());
+ canvas.drawCircle(mCircleX.get(), mCircleY.get(), mCircleRadius.get(), mPaint.get());
+ });
+ canvas.drawRenderNode(content.get());
+ }
+
+ void doFrame(int frameNr) override {
+ float value = (abs((frameNr % 200) - 100)) / 100.0f;
+ mRoundRectRx->value = dp(10) + value * dp(40);
+ mRoundRectRy->value = dp(10) + value * dp(80);
+ mCircleRadius->value = value * dp(200);
+ content->setPropertyFieldsDirty(RenderNode::GENERIC);
+ }
+};
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
index 84aae75..05b4a72 100644
--- a/libs/hwui/utils/TestWindowContext.cpp
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -180,6 +180,10 @@
TestWindowContext::TestWindowContext() :
mData (nullptr) { }
+TestWindowContext::~TestWindowContext() {
+ delete mData;
+}
+
void TestWindowContext::initialize(int width, int height) {
mData = new TestWindowData(SkISize::Make(width, height));
}
diff --git a/libs/hwui/utils/TestWindowContext.h b/libs/hwui/utils/TestWindowContext.h
index 445a11b..48ec952 100644
--- a/libs/hwui/utils/TestWindowContext.h
+++ b/libs/hwui/utils/TestWindowContext.h
@@ -35,6 +35,7 @@
public:
TestWindowContext();
+ ~TestWindowContext();
/// We need to know the size of the window.
void initialize(int width, int height);
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index a046512..bcc2b406 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.DrawableRes;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
@@ -41,6 +42,8 @@
import android.util.Log;
import android.view.Display;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -201,6 +204,7 @@
info.mDescription = sStatic.mResources.getText(
com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
+ info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH;
sStatic.mBluetoothA2dpRoute = info;
addRouteStatic(sStatic.mBluetoothA2dpRoute);
} else {
@@ -480,6 +484,7 @@
route.mName = globalRoute.name;
route.mDescription = globalRoute.description;
route.mSupportedTypes = globalRoute.supportedTypes;
+ route.mDeviceType = globalRoute.deviceType;
route.mEnabled = globalRoute.enabled;
route.setRealStatusCode(globalRoute.statusCode);
route.mPlaybackType = globalRoute.playbackType;
@@ -1411,6 +1416,7 @@
newRoute.mDescription = sStatic.mResources.getText(
com.android.internal.R.string.wireless_display_route_description);
newRoute.updatePresentationDisplay();
+ newRoute.mDeviceType = RouteInfo.DEVICE_TYPE_TV;
return newRoute;
}
@@ -1470,6 +1476,7 @@
CharSequence mDescription;
private CharSequence mStatus;
int mSupportedTypes;
+ int mDeviceType;
RouteGroup mGroup;
final RouteCategory mCategory;
Drawable mIcon;
@@ -1502,6 +1509,42 @@
/** @hide */ public static final int STATUS_IN_USE = 5;
/** @hide */ public static final int STATUS_CONNECTED = 6;
+ /** @hide */
+ @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceType {}
+
+ /**
+ * The default receiver device type of the route indicating the type is unknown.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_UNKNOWN = 0;
+
+ /**
+ * A receiver device type of the route indicating the presentation of the media is happening
+ * on a TV.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_TV = 1;
+
+ /**
+ * A receiver device type of the route indicating the presentation of the media is happening
+ * on a speaker.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_SPEAKER = 2;
+
+ /**
+ * A receiver device type of the route indicating the presentation of the media is happening
+ * on a bluetooth device such as a bluetooth speaker.
+ *
+ * @see #getDeviceType
+ */
+ public static final int DEVICE_TYPE_BLUETOOTH = 3;
+
private Object mTag;
/**
@@ -1533,6 +1576,7 @@
RouteInfo(RouteCategory category) {
mCategory = category;
+ mDeviceType = DEVICE_TYPE_UNKNOWN;
}
/**
@@ -1670,6 +1714,18 @@
return mSupportedTypes;
}
+ /**
+ * Gets the type of the receiver device associated with this route.
+ *
+ * @return The type of the receiver device associated with this route:
+ * {@link #DEVICE_TYPE_BLUETOOTH}, {@link #DEVICE_TYPE_TV}, {@link #DEVICE_TYPE_SPEAKER},
+ * or {@link #DEVICE_TYPE_UNKNOWN}.
+ */
+ @DeviceType
+ public int getDeviceType() {
+ return mDeviceType;
+ }
+
/** @hide */
public boolean matchesTypes(int types) {
return (mSupportedTypes & types) != 0;
diff --git a/media/java/android/media/MediaRouterClientState.java b/media/java/android/media/MediaRouterClientState.java
index 54b8276..34e18f6 100644
--- a/media/java/android/media/MediaRouterClientState.java
+++ b/media/java/android/media/MediaRouterClientState.java
@@ -104,6 +104,7 @@
public int volumeMax;
public int volumeHandling;
public int presentationDisplayId;
+ public @MediaRouter.RouteInfo.DeviceType int deviceType;
public RouteInfo(String id) {
this.id = id;
@@ -113,6 +114,7 @@
playbackStream = -1;
volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
presentationDisplayId = -1;
+ deviceType = MediaRouter.RouteInfo.DEVICE_TYPE_UNKNOWN;
}
public RouteInfo(RouteInfo other) {
@@ -128,6 +130,7 @@
volumeMax = other.volumeMax;
volumeHandling = other.volumeHandling;
presentationDisplayId = other.presentationDisplayId;
+ deviceType = other.deviceType;
}
RouteInfo(Parcel in) {
@@ -143,6 +146,7 @@
volumeMax = in.readInt();
volumeHandling = in.readInt();
presentationDisplayId = in.readInt();
+ deviceType = in.readInt();
}
@Override
@@ -164,6 +168,7 @@
dest.writeInt(volumeMax);
dest.writeInt(volumeHandling);
dest.writeInt(presentationDisplayId);
+ dest.writeInt(deviceType);
}
@Override
@@ -180,6 +185,7 @@
+ ", volumeMax=" + volumeMax
+ ", volumeHandling=" + volumeHandling
+ ", presentationDisplayId=" + presentationDisplayId
+ + ", deviceType=" + deviceType
+ " }";
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 2d77c6a..770d011 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -316,12 +316,8 @@
mAdapter = new DocumentsAdapter(context);
mRecView.setAdapter(mAdapter);
- mDefaultItemColor = context.getResources().getColor(android.R.color.transparent);
- // Get the accent color.
- TypedValue selColor = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
- // Set the opacity to 10%.
- mSelectedItemColor = (selColor.data & 0x00ffffff) | 0x16000000;
+ mDefaultItemColor = context.getResources().getColor(R.color.item_doc_background);
+ mSelectedItemColor = context.getResources().getColor(R.color.item_doc_background_selected);
GestureDetector.SimpleOnGestureListener listener =
new GestureDetector.SimpleOnGestureListener() {
@@ -943,6 +939,7 @@
public void setSelected(boolean selected) {
itemView.setActivated(selected);
+ itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
}
@Override
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java b/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java
new file mode 100644
index 0000000..4b87da4
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java
@@ -0,0 +1,186 @@
+package com.android.providers.settings;
+
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Backup/Restore Serializer Class for android.net.NetworkPolicy
+ */
+public class NetworkPolicySerializer {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "NetworkPolicySerializer";
+
+ private static final int NULL = 0;
+ private static final int NOT_NULL = 1;
+ /**
+ * Current Version of the Serializer.
+ */
+ private static int STATE_VERSION = 1;
+
+ /**
+ * Marshals an array of NetworkPolicy objects into a byte-array.
+ *
+ * @param policies - NetworkPolicies to be Marshaled
+ * @return byte array
+ */
+
+ public static byte[] marshalNetworkPolicies(NetworkPolicy policies[]) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ if (policies != null && policies.length != 0) {
+ DataOutputStream out = new DataOutputStream(baos);
+ try {
+ out.writeInt(STATE_VERSION);
+ out.writeInt(policies.length);
+ for (NetworkPolicy policy : policies) {
+ byte[] marshaledPolicy = marshalNetworkPolicy(policy);
+ if (marshaledPolicy != null) {
+ out.writeByte(NOT_NULL);
+ out.writeInt(marshaledPolicy.length);
+ out.write(marshaledPolicy);
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to Convert NetworkPolicies to byte array", ioe);
+ baos.reset();
+ }
+ }
+ return baos.toByteArray();
+ }
+
+ /**
+ * Unmarshals a byte array into an array of NetworkPolicy Objects
+ *
+ * @param data - marshaled NetworkPolicies Array
+ * @return NetworkPolicy[] array
+ */
+ public static NetworkPolicy[] unmarshalNetworkPolicies(byte[] data) {
+ if (data == null || data.length == 0) {
+ return new NetworkPolicy[0];
+ }
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
+ try {
+ int version = in.readInt();
+ int length = in.readInt();
+ NetworkPolicy[] policies = new NetworkPolicy[length];
+ for (int i = 0; i < length; i++) {
+ byte isNull = in.readByte();
+ if (isNull == NULL) continue;
+ int byteLength = in.readInt();
+ byte[] policyData = new byte[byteLength];
+ in.read(policyData, 0, byteLength);
+ policies[i] = unmarshalNetworkPolicy(policyData);
+ }
+ return policies;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to Convert byte array to NetworkPolicies", ioe);
+ return new NetworkPolicy[0];
+ }
+ }
+
+ /**
+ * Marshals a NetworkPolicy object into a byte-array.
+ *
+ * @param networkPolicy - NetworkPolicy to be Marshaled
+ * @return byte array
+ */
+ public static byte[] marshalNetworkPolicy(NetworkPolicy networkPolicy) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ if (networkPolicy != null) {
+ DataOutputStream out = new DataOutputStream(baos);
+ try {
+ out.writeInt(STATE_VERSION);
+ writeNetworkTemplate(out, networkPolicy.template);
+ out.writeInt(networkPolicy.cycleDay);
+ writeString(out, networkPolicy.cycleTimezone);
+ out.writeLong(networkPolicy.warningBytes);
+ out.writeLong(networkPolicy.limitBytes);
+ out.writeLong(networkPolicy.lastWarningSnooze);
+ out.writeLong(networkPolicy.lastLimitSnooze);
+ out.writeInt(networkPolicy.metered ? 1 : 0);
+ out.writeInt(networkPolicy.inferred ? 1 : 0);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to Convert NetworkPolicy to byte array", ioe);
+ baos.reset();
+ }
+ }
+ return baos.toByteArray();
+ }
+
+ /**
+ * Unmarshals a byte array into a NetworkPolicy Object
+ *
+ * @param data - marshaled NetworkPolicy Object
+ * @return NetworkPolicy Object
+ */
+ public static NetworkPolicy unmarshalNetworkPolicy(byte[] data) {
+ if (data == null || data.length == 0) {
+ return null;
+ }
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
+ try {
+ int version = in.readInt();
+ NetworkTemplate template = readNetworkTemplate(in, version);
+ int cycleDay = in.readInt();
+ String cycleTimeZone = readString(in, version);
+ long warningBytes = in.readLong();
+ long limitBytes = in.readLong();
+ long lastWarningSnooze = in.readLong();
+ long lastLimitSnooze = in.readLong();
+ boolean metered = in.readInt() == 1;
+ boolean inferred = in.readInt() == 1;
+ return new NetworkPolicy(template, cycleDay, cycleTimeZone, warningBytes, limitBytes,
+ lastWarningSnooze, lastLimitSnooze, metered, inferred);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to Convert byte array to NetworkPolicy", ioe);
+ return null;
+ }
+ }
+
+ private static NetworkTemplate readNetworkTemplate(DataInputStream in, int version)
+ throws IOException {
+ byte isNull = in.readByte();
+ if (isNull == NULL) return null;
+ int matchRule = in.readInt();
+ String subscriberId = readString(in, version);
+ String networkId = readString(in, version);
+ return new NetworkTemplate(matchRule, subscriberId, networkId);
+ }
+
+ private static void writeNetworkTemplate(DataOutputStream out, NetworkTemplate template)
+ throws IOException {
+ if (template != null) {
+ out.writeByte(NOT_NULL);
+ out.writeInt(template.getMatchRule());
+ writeString(out, template.getSubscriberId());
+ writeString(out, template.getNetworkId());
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+
+ private static String readString(DataInputStream in, int version) throws IOException {
+ byte isNull = in.readByte();
+ if (isNull == NOT_NULL) {
+ return in.readUTF();
+ }
+ return null;
+ }
+
+ private static void writeString(DataOutputStream out, String val) throws IOException {
+ if (val != null) {
+ out.writeByte(NOT_NULL);
+ out.writeUTF(val);
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 2e96f18..185a03f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -24,6 +24,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.net.NetworkPolicyManager;
import android.net.Uri;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
@@ -81,10 +82,13 @@
private static final String KEY_GLOBAL = "global";
private static final String KEY_LOCALE = "locale";
private static final String KEY_LOCK_SETTINGS = "lock_settings";
+ private static final String KEY_SOFTAP_CONFIG = "softap_config";
+ private static final String KEY_NET_POLICIES = "network_policies";
+
// Versioning of the state file. Increment this version
// number any time the set of state items is altered.
- private static final int STATE_VERSION = 4;
+ private static final int STATE_VERSION = 6;
// Slots in the checksum array. Never insert new items in the middle
// of this array; new slots must be appended.
@@ -95,22 +99,28 @@
private static final int STATE_WIFI_CONFIG = 4;
private static final int STATE_GLOBAL = 5;
private static final int STATE_LOCK_SETTINGS = 6;
+ private static final int STATE_SOFTAP_CONFIG = 7;
+ private static final int STATE_NET_POLICIES = 8;
- private static final int STATE_SIZE = 7; // The current number of state items
+ private static final int STATE_SIZE = 9; // The current number of state items
// Number of entries in the checksum array at various version numbers
private static final int STATE_SIZES[] = {
- 0,
- 4, // version 1
- 5, // version 2 added STATE_WIFI_CONFIG
- 6, // version 3 added STATE_GLOBAL
- STATE_SIZE // version 4 added STATE_LOCK_SETTINGS
+ 0,
+ 4, // version 1
+ 5, // version 2 added STATE_WIFI_CONFIG
+ 6, // version 3 added STATE_GLOBAL
+ 7, // version 4 added STATE_LOCK_SETTINGS
+ 8, // version 5 added STATE_SOFTAP_CONFIG
+ STATE_SIZE // version 6 added STATE_NET_POLICIES
};
// Versioning of the 'full backup' format
private static final int FULL_BACKUP_VERSION = 3;
private static final int FULL_BACKUP_ADDED_GLOBAL = 2; // added the "global" entry
private static final int FULL_BACKUP_ADDED_LOCK_SETTINGS = 3; // added the "lock_settings" entry
+ private static final int FULL_BACKUP_ADDED_SOFTAP_CONF = 4; //added the "softap_config" entry
+ private static final int FULL_BACKUP_ADDED_NET_POLICIES = 5; //added the "network_policies" entry
private static final int INTEGER_BYTE_COUNT = Integer.SIZE / Byte.SIZE;
@@ -119,8 +129,8 @@
private static final String TAG = "SettingsBackupAgent";
private static final String[] PROJECTION = {
- Settings.NameValueTable.NAME,
- Settings.NameValueTable.VALUE
+ Settings.NameValueTable.NAME,
+ Settings.NameValueTable.VALUE
};
private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf";
@@ -396,7 +406,7 @@
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState) throws IOException {
+ ParcelFileDescriptor newState) throws IOException {
byte[] systemSettingsData = getSystemSettings();
byte[] secureSettingsData = getSecureSettings();
@@ -405,26 +415,34 @@
byte[] locale = mSettingsHelper.getLocaleData();
byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
byte[] wifiConfigData = getFileData(mWifiConfigFile);
+ byte[] softApConfigData = getSoftAPConfiguration();
+ byte[] netPoliciesData = getNetworkPolicies();
long[] stateChecksums = readOldChecksums(oldState);
stateChecksums[STATE_SYSTEM] =
- writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data);
+ writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data);
stateChecksums[STATE_SECURE] =
- writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data);
+ writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data);
stateChecksums[STATE_GLOBAL] =
- writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, globalSettingsData, data);
+ writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, globalSettingsData, data);
stateChecksums[STATE_LOCALE] =
- writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
+ writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
stateChecksums[STATE_WIFI_SUPPLICANT] =
- writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT,
- wifiSupplicantData, data);
+ writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT,
+ wifiSupplicantData, data);
stateChecksums[STATE_WIFI_CONFIG] =
- writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData,
- data);
+ writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData,
+ data);
stateChecksums[STATE_LOCK_SETTINGS] =
- writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS,
- lockSettingsData, data);
+ writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS,
+ lockSettingsData, data);
+ stateChecksums[STATE_SOFTAP_CONFIG] =
+ writeIfChanged(stateChecksums[STATE_SOFTAP_CONFIG], KEY_SOFTAP_CONFIG,
+ softApConfigData, data);
+ stateChecksums[STATE_NET_POLICIES] =
+ writeIfChanged(stateChecksums[STATE_NET_POLICIES], KEY_NET_POLICIES,
+ netPoliciesData, data);
writeNewChecksums(stateChecksums, newState);
}
@@ -504,7 +522,7 @@
restoredSupplicantData, restoredSupplicantData.length);
FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
FileUtils.S_IRUSR | FileUtils.S_IWUSR |
- FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+ FileUtils.S_IRGRP | FileUtils.S_IWGRP,
Process.myUid(), Process.WIFI_UID);
}
if (restoredWifiConfigFile != null) {
@@ -533,7 +551,7 @@
@Override
public void onRestore(BackupDataInput data, int appVersionCode,
- ParcelFileDescriptor newState) throws IOException {
+ ParcelFileDescriptor newState) throws IOException {
HashSet<String> movedToGlobal = new HashSet<String>();
Settings.System.getMovedToGlobalSettings(movedToGlobal);
@@ -561,7 +579,15 @@
mWifiRestore.incorporateWifiConfigFile(data);
} else if (KEY_LOCK_SETTINGS.equals(key)) {
restoreLockSettings(data);
- } else {
+ } else if (KEY_SOFTAP_CONFIG.equals(key)){
+ byte[] softapData = new byte[size];
+ data.readEntityData(softapData, 0, size);
+ restoreSoftApConfiguration(softapData);
+ } else if (KEY_NET_POLICIES.equals(key)) {
+ byte[] netPoliciesData = new byte[size];
+ data.readEntityData(netPoliciesData, 0, size);
+ restoreNetworkPolicies(netPoliciesData);
+ } else {
data.skipEntityData();
}
}
@@ -589,6 +615,8 @@
byte[] locale = mSettingsHelper.getLocaleData();
byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
byte[] wifiConfigData = getFileData(mWifiConfigFile);
+ byte[] softApConfigData = getSoftAPConfiguration();
+ byte[] netPoliciesData = getNetworkPolicies();
// Write the data to the staging file, then emit that as our tarfile
// representation of the backed-up settings.
@@ -623,6 +651,12 @@
if (DEBUG_BACKUP) Log.d(TAG, lockSettingsData.length + " bytes of lock settings data");
out.writeInt(lockSettingsData.length);
out.write(lockSettingsData);
+ if (DEBUG_BACKUP) Log.d(TAG, softApConfigData.length + " bytes of softap config data");
+ out.writeInt(softApConfigData.length);
+ out.write(softApConfigData);
+ if (DEBUG_BACKUP) Log.d(TAG, netPoliciesData.length + " bytes of network policies data");
+ out.writeInt(netPoliciesData.length);
+ out.write(netPoliciesData);
out.flush(); // also flushes downstream
@@ -635,7 +669,7 @@
@Override
public void onRestoreFile(ParcelFileDescriptor data, long size,
- int type, String domain, String relpath, long mode, long mtime)
+ int type, String domain, String relpath, long mode, long mtime)
throws IOException {
if (DEBUG_BACKUP) Log.d(TAG, "onRestoreFile() invoked");
// Our data is actually a blob of flattened settings data identical to that
@@ -692,7 +726,7 @@
restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes);
FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
FileUtils.S_IRUSR | FileUtils.S_IWUSR |
- FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+ FileUtils.S_IRGRP | FileUtils.S_IWGRP,
Process.myUid(), Process.WIFI_UID);
// retain the previous WIFI state.
enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED ||
@@ -715,6 +749,26 @@
}
}
+ if (version >= FULL_BACKUP_ADDED_SOFTAP_CONF){
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of softap config data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ if (nBytes > 0) {
+ in.readFully(buffer, 0, nBytes);
+ restoreSoftApConfiguration(buffer);
+ }
+ }
+
+ if (version >= FULL_BACKUP_ADDED_NET_POLICIES){
+ nBytes = in.readInt();
+ if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of network policies data");
+ if (nBytes > buffer.length) buffer = new byte[nBytes];
+ if (nBytes > 0) {
+ in.readFully(buffer, 0, nBytes);
+ restoreNetworkPolicies(buffer);
+ }
+ }
+
if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
} else {
data.close();
@@ -754,7 +808,7 @@
}
private long writeIfChanged(long oldChecksum, String key, byte[] data,
- BackupDataOutput output) {
+ BackupDataOutput output) {
CRC32 checkSummer = new CRC32();
checkSummer.update(data);
long newChecksum = checkSummer.getValue();
@@ -829,7 +883,7 @@
}
private void restoreSettings(BackupDataInput data, Uri contentUri,
- HashSet<String> movedToGlobal) {
+ HashSet<String> movedToGlobal) {
byte[] settings = new byte[data.getDataSize()];
try {
data.readEntityData(settings, 0, settings.length);
@@ -841,7 +895,7 @@
}
private void restoreSettings(byte[] settings, int bytes, Uri contentUri,
- HashSet<String> movedToGlobal) {
+ HashSet<String> movedToGlobal) {
if (DEBUG) {
Log.i(TAG, "restoreSettings: " + contentUri);
}
@@ -1160,6 +1214,30 @@
}
}
+ private byte[] getSoftAPConfiguration(){
+ WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+ return WiFiConfigurationSerializer.marshalWifiConfig(wifiManager.getWifiApConfiguration());
+ }
+
+ private void restoreSoftApConfiguration(byte[] data){
+ WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+ wifiManager.setWifiApConfiguration(WiFiConfigurationSerializer.unmarshalWifiConfig(data));
+ }
+
+ private byte[] getNetworkPolicies(){
+ NetworkPolicyManager networkPolicyManager =
+ (NetworkPolicyManager)getSystemService(NETWORK_POLICY_SERVICE);
+ return NetworkPolicySerializer
+ .marshalNetworkPolicies(networkPolicyManager.getNetworkPolicies());
+ }
+
+ private void restoreNetworkPolicies(byte[] data){
+ NetworkPolicyManager networkPolicyManager =
+ (NetworkPolicyManager)getSystemService(NETWORK_POLICY_SERVICE);
+ networkPolicyManager
+ .setNetworkPolicies(NetworkPolicySerializer.unmarshalNetworkPolicies(data));
+ }
+
/**
* Write an int in BigEndian into the byte array.
* @param out byte array
@@ -1181,8 +1259,7 @@
}
private int readInt(byte[] in, int pos) {
- int result =
- ((in[pos ] & 0xFF) << 24) |
+ int result = ((in[pos ] & 0xFF) << 24) |
((in[pos + 1] & 0xFF) << 16) |
((in[pos + 2] & 0xFF) << 8) |
((in[pos + 3] & 0xFF) << 0);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java b/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java
new file mode 100644
index 0000000..f9f1d3f
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.settings;
+
+import android.net.IpConfiguration;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.BitSet;
+
+
+/**
+ * Backup/Restore Serializer Class for com.android.net.wifi.WifiConfiguration
+ */
+public class WiFiConfigurationSerializer {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "WiFiConfigSerializer";
+
+ private static final int NULL = 0;
+ private static final int NOT_NULL = 1;
+ /**
+ * Current Version of the Serializer.
+ */
+ private static int STATE_VERSION = 1;
+
+
+ /**
+ * Marshals a WifiConfig object into a byte-array.
+ *
+ * @param wifiConfig - WifiConfiguration to be Marshalled
+ * @return byte array
+ */
+
+ public static byte[] marshalWifiConfig(WifiConfiguration wifiConfig) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ if(wifiConfig != null) {
+ DataOutputStream out = new DataOutputStream(baos);
+ try {
+ out.writeInt(STATE_VERSION);
+ out.writeInt(wifiConfig.networkId);
+ out.writeInt(wifiConfig.status);
+ out.writeInt(wifiConfig.disableReason);
+ writeString(out, wifiConfig.SSID);
+ writeString(out, wifiConfig.BSSID);
+ out.writeInt(wifiConfig.apBand);
+ out.writeInt(wifiConfig.apChannel);
+ writeString(out, wifiConfig.autoJoinBSSID);
+ writeString(out, wifiConfig.FQDN);
+ writeString(out, wifiConfig.providerFriendlyName);
+ out.writeInt(wifiConfig.roamingConsortiumIds.length);
+ for (long id : wifiConfig.roamingConsortiumIds) {
+ out.writeLong(id);
+ }
+ writeString(out, wifiConfig.preSharedKey);
+ for (String wepKey : wifiConfig.wepKeys) {
+ writeString(out, wepKey);
+ }
+ out.writeInt(wifiConfig.wepTxKeyIndex);
+ out.writeInt(wifiConfig.priority);
+ out.writeInt(wifiConfig.hiddenSSID ? 1 : 0);
+ out.writeInt(wifiConfig.requirePMF ? 1 : 0);
+ writeString(out, wifiConfig.updateIdentifier);
+
+ writeBitSet(out, wifiConfig.allowedKeyManagement);
+ writeBitSet(out, wifiConfig.allowedProtocols);
+ writeBitSet(out, wifiConfig.allowedAuthAlgorithms);
+ writeBitSet(out, wifiConfig.allowedPairwiseCiphers);
+ writeBitSet(out, wifiConfig.allowedGroupCiphers);
+
+
+ //IpConfiguration
+ writeIpConfiguration(out, wifiConfig.getIpConfiguration());
+
+ writeString(out, wifiConfig.dhcpServer);
+ writeString(out, wifiConfig.defaultGwMacAddress);
+ out.writeInt(wifiConfig.autoJoinStatus);
+ out.writeInt(wifiConfig.selfAdded ? 1 : 0);
+ out.writeInt(wifiConfig.didSelfAdd ? 1 : 0);
+ out.writeInt(wifiConfig.validatedInternetAccess ? 1 : 0);
+ out.writeInt(wifiConfig.ephemeral ? 1 : 0);
+ out.writeInt(wifiConfig.creatorUid);
+ out.writeInt(wifiConfig.lastConnectUid);
+ out.writeInt(wifiConfig.lastUpdateUid);
+ writeString(out, wifiConfig.creatorName);
+ writeString(out, wifiConfig.lastUpdateName);
+ out.writeLong(wifiConfig.blackListTimestamp);
+ out.writeLong(wifiConfig.lastConnectionFailure);
+ out.writeLong(wifiConfig.lastRoamingFailure);
+ out.writeInt(wifiConfig.lastRoamingFailureReason);
+ out.writeLong(wifiConfig.roamingFailureBlackListTimeMilli);
+ out.writeLong(wifiConfig.numConnectionFailures);
+ out.writeLong(wifiConfig.numIpConfigFailures);
+ out.writeInt(wifiConfig.numAuthFailures);
+ out.writeInt(wifiConfig.numScorerOverride);
+ out.writeInt(wifiConfig.numScorerOverrideAndSwitchedNetwork);
+ out.writeInt(wifiConfig.numAssociation);
+ out.writeInt(wifiConfig.numUserTriggeredWifiDisableLowRSSI);
+ out.writeInt(wifiConfig.numUserTriggeredWifiDisableBadRSSI);
+ out.writeInt(wifiConfig.numUserTriggeredWifiDisableNotHighRSSI);
+ out.writeInt(wifiConfig.numTicksAtLowRSSI);
+ out.writeInt(wifiConfig.numTicksAtBadRSSI);
+ out.writeInt(wifiConfig.numTicksAtNotHighRSSI);
+ out.writeInt(wifiConfig.numUserTriggeredJoinAttempts);
+ out.writeInt(wifiConfig.autoJoinUseAggressiveJoinAttemptThreshold);
+ out.writeInt(wifiConfig.autoJoinBailedDueToLowRssi ? 1 : 0);
+ out.writeInt(wifiConfig.userApproved);
+ out.writeInt(wifiConfig.numNoInternetAccessReports);
+ out.writeInt(wifiConfig.noInternetAccessExpected ? 1 : 0);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to Convert WifiConfiguration to byte array", ioe);
+ baos.reset();
+ }
+ }
+ return baos.toByteArray();
+ }
+
+ /**
+ * Unmarshals a byte array into a WifiConfig Object
+ *
+ * @param data - marshalled WifiConfig Object
+ * @return WifiConfiguration Object
+ */
+
+ public static WifiConfiguration unmarshalWifiConfig(byte[] data) {
+ if (data == null || data.length == 0) {
+ return null;
+ }
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
+ WifiConfiguration config = new WifiConfiguration();
+ try {
+ int version = in.readInt();
+
+ config.networkId = in.readInt();
+ config.status = in.readInt();
+ config.disableReason = in.readInt();
+ config.SSID = readString(in, version);
+ config.BSSID = readString(in, version);
+ config.apBand = in.readInt();
+ config.apChannel = in.readInt();
+ config.autoJoinBSSID = readString(in, version);
+ config.FQDN = readString(in, version);
+ config.providerFriendlyName = readString(in, version);
+ int numRoamingConsortiumIds = in.readInt();
+ config.roamingConsortiumIds = new long[numRoamingConsortiumIds];
+ for (int i = 0; i < numRoamingConsortiumIds; i++) {
+ config.roamingConsortiumIds[i] = in.readLong();
+ }
+ config.preSharedKey = readString(in, version);
+ for (int i = 0; i < config.wepKeys.length; i++) {
+ config.wepKeys[i] = readString(in, version);
+ }
+ config.wepTxKeyIndex = in.readInt();
+ config.priority = in.readInt();
+ config.hiddenSSID = in.readInt() != 0;
+ config.requirePMF = in.readInt() != 0;
+ config.updateIdentifier = readString(in, version);
+
+ config.allowedKeyManagement = readBitSet(in, version);
+ config.allowedProtocols = readBitSet(in, version);
+ config.allowedAuthAlgorithms = readBitSet(in, version);
+ config.allowedPairwiseCiphers = readBitSet(in, version);
+ config.allowedGroupCiphers = readBitSet(in, version);
+
+ //Not backed-up because EnterpriseConfig involves
+ //Certificates which are device specific.
+ //config.enterpriseConfig = new WifiEnterpriseConfig();
+
+ config.setIpConfiguration(readIpConfiguration(in, version));
+
+
+ config.dhcpServer = readString(in, version);
+ config.defaultGwMacAddress = readString(in, version);
+ config.autoJoinStatus = in.readInt();
+ config.selfAdded = in.readInt() != 0;
+ config.didSelfAdd = in.readInt() != 0;
+ config.validatedInternetAccess = in.readInt() != 0;
+ config.ephemeral = in.readInt() != 0;
+ config.creatorUid = in.readInt();
+ config.lastConnectUid = in.readInt();
+ config.lastUpdateUid = in.readInt();
+ config.creatorName = readString(in, version);
+ config.lastUpdateName = readString(in, version);
+ config.blackListTimestamp = in.readLong();
+ config.lastConnectionFailure = in.readLong();
+ config.lastRoamingFailure = in.readLong();
+ config.lastRoamingFailureReason = in.readInt();
+ config.roamingFailureBlackListTimeMilli = in.readLong();
+ config.numConnectionFailures = in.readInt();
+ config.numIpConfigFailures = in.readInt();
+ config.numAuthFailures = in.readInt();
+ config.numScorerOverride = in.readInt();
+ config.numScorerOverrideAndSwitchedNetwork = in.readInt();
+ config.numAssociation = in.readInt();
+ config.numUserTriggeredWifiDisableLowRSSI = in.readInt();
+ config.numUserTriggeredWifiDisableBadRSSI = in.readInt();
+ config.numUserTriggeredWifiDisableNotHighRSSI = in.readInt();
+ config.numTicksAtLowRSSI = in.readInt();
+ config.numTicksAtBadRSSI = in.readInt();
+ config.numTicksAtNotHighRSSI = in.readInt();
+ config.numUserTriggeredJoinAttempts = in.readInt();
+ config.autoJoinUseAggressiveJoinAttemptThreshold = in.readInt();
+ config.autoJoinBailedDueToLowRssi = in.readInt() != 0;
+ config.userApproved = in.readInt();
+ config.numNoInternetAccessReports = in.readInt();
+ config.noInternetAccessExpected = in.readInt() != 0;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Failed to convert byte array to WifiConfiguration object", ioe);
+ return null;
+ }
+ return config;
+ }
+
+ private static ProxyInfo readProxyInfo(DataInputStream in, int version) throws IOException {
+ int isNull = in.readByte();
+ if (isNull == NULL) return null;
+ String host = readString(in, version);
+ int port = in.readInt();
+ String exclusionList = readString(in, version);
+ return new ProxyInfo(host, port, exclusionList);
+ }
+
+ private static void writeProxyInfo(DataOutputStream out, ProxyInfo proxyInfo) throws IOException {
+ if (proxyInfo != null) {
+ out.writeByte(NOT_NULL);
+ writeString(out, proxyInfo.getHost());
+ out.writeInt(proxyInfo.getPort());
+ writeString(out, proxyInfo.getExclusionListAsString());
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+
+ private static InetAddress readInetAddress(DataInputStream in, int version) throws IOException {
+ int isNull = in.readByte();
+ if (isNull == NULL) return null;
+ InetAddress address = null;
+ int addressLength = in.readInt();
+ if (addressLength < 1) return address;
+ byte[] addressBytes = new byte[addressLength];
+ in.read(addressBytes, 0, addressLength);
+ try {
+ address = InetAddress.getByAddress(addressBytes);
+ } catch (UnknownHostException unknownHostException) {
+ return null;
+ }
+ return address;
+ }
+
+ private static void writeInetAddress(DataOutputStream out, InetAddress address) throws IOException {
+ if (address.getAddress() != null) {
+ out.writeByte(NOT_NULL);
+ out.writeInt(address.getAddress().length);
+ out.write(address.getAddress(), 0, address.getAddress().length);
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+
+ private static LinkAddress readLinkAddress(DataInputStream in, int version) throws IOException {
+ int isNull = in.readByte();
+ if (isNull == NULL) return null;
+ InetAddress address = readInetAddress(in, version);
+ int prefixLength = in.readInt();
+ int flags = in.readInt();
+ int scope = in.readInt();
+ return new LinkAddress(address, prefixLength, flags, scope);
+ }
+
+ private static void writeLinkAddress(DataOutputStream out, LinkAddress address) throws IOException {
+ if (address != null) {
+ out.writeByte(NOT_NULL);
+ writeInetAddress(out, address.getAddress());
+ out.writeInt(address.getPrefixLength());
+ out.writeInt(address.getFlags());
+ out.writeInt(address.getScope());
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+
+ private static StaticIpConfiguration readStaticIpConfiguration(DataInputStream in, int version) throws IOException {
+ int isNull = in.readByte();
+ if (isNull == NULL) return null;
+ StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+ staticIpConfiguration.ipAddress = readLinkAddress(in, version);
+ staticIpConfiguration.gateway = readInetAddress(in, version);
+ int dnsServersLength = in.readInt();
+ for (int i = 0; i < dnsServersLength; i++) {
+ staticIpConfiguration.dnsServers.add(readInetAddress(in, version));
+ }
+ staticIpConfiguration.domains = readString(in, version);
+ return staticIpConfiguration;
+ }
+
+ private static void writeStaticIpConfiguration(DataOutputStream out, StaticIpConfiguration staticIpConfiguration) throws IOException {
+ if (staticIpConfiguration != null) {
+ out.writeByte(NOT_NULL);
+ writeLinkAddress(out, staticIpConfiguration.ipAddress);
+ writeInetAddress(out, staticIpConfiguration.gateway);
+ out.writeInt(staticIpConfiguration.dnsServers.size());
+ for (InetAddress inetAddress : staticIpConfiguration.dnsServers) {
+ writeInetAddress(out, inetAddress);
+ }
+ writeString(out, staticIpConfiguration.domains);
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+
+ private static IpConfiguration readIpConfiguration(DataInputStream in, int version) throws IOException {
+ int isNull = in.readByte();
+ if (isNull == NULL) return null;
+ IpConfiguration ipConfiguration = new IpConfiguration();
+ String tmp = readString(in, version);
+ ipConfiguration.ipAssignment = tmp == null ? null : IpConfiguration.IpAssignment.valueOf(tmp);
+ tmp = readString(in, version);
+ ipConfiguration.proxySettings = tmp == null ? null : IpConfiguration.ProxySettings.valueOf(tmp);
+ ipConfiguration.staticIpConfiguration = readStaticIpConfiguration(in, version);
+ ipConfiguration.httpProxy = readProxyInfo(in, version);
+ return ipConfiguration;
+ }
+
+
+ private static void writeIpConfiguration(DataOutputStream out, IpConfiguration ipConfiguration) throws IOException {
+ if (ipConfiguration != null) {
+ out.writeByte(NOT_NULL);
+ writeString(out, ipConfiguration.ipAssignment != null ? ipConfiguration.ipAssignment.name() : null);
+ writeString(out, ipConfiguration.proxySettings != null ? ipConfiguration.proxySettings.name() : null);
+ writeStaticIpConfiguration(out, ipConfiguration.staticIpConfiguration);
+ writeProxyInfo(out, ipConfiguration.httpProxy);
+ } else {
+ out.writeByte(NULL);
+ }
+
+ }
+
+ private static String readString(DataInputStream in, int version) throws IOException {
+ byte isNull = in.readByte();
+ if (isNull == NOT_NULL) {
+ return in.readUTF();
+ }
+ return null;
+ }
+
+ private static void writeString(DataOutputStream out, String val) throws IOException {
+ if (val != null) {
+ out.writeByte(NOT_NULL);
+ out.writeUTF(val);
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+
+ private static BitSet readBitSet(DataInputStream in, int version) throws IOException {
+ byte isNull = in.readByte();
+ if (isNull == NOT_NULL) {
+ int length = in.readInt();
+ byte[] bytes = new byte[length];
+ in.read(bytes, 0, length);
+ return BitSet.valueOf(bytes);
+ }
+ return new BitSet();
+ }
+
+ private static void writeBitSet(DataOutputStream out, BitSet val) throws IOException {
+ if (val != null) {
+ out.writeByte(NOT_NULL);
+ byte[] byteArray = val.toByteArray();
+ out.writeInt(byteArray.length);
+ out.write(byteArray);
+ } else {
+ out.writeByte(NULL);
+ }
+ }
+}
diff --git a/packages/SettingsProvider/test/Android.mk b/packages/SettingsProvider/test/Android.mk
index ef863e7..f278967 100644
--- a/packages/SettingsProvider/test/Android.mk
+++ b/packages/SettingsProvider/test/Android.mk
@@ -5,7 +5,10 @@
# Note we statically link SettingsState to do some unit tests. It's not accessible otherwise
# because this test is not an instrumentation test. (because the target runs in the system process.)
LOCAL_SRC_FILES := $(call all-subdir-java-files) \
- ../src/com/android/providers/settings/SettingsState.java
+ ../src/com/android/providers/settings/SettingsState.java \
+ ../src/com/android/providers/settings/WiFiConfigurationSerializer.java \
+ ../src/com/android/providers/settings/NetworkPolicySerializer.java
+
LOCAL_PACKAGE_NAME := SettingsProviderTest
@@ -13,4 +16,4 @@
LOCAL_CERTIFICATE := platform
-include $(BUILD_PACKAGE)
\ No newline at end of file
+include $(BUILD_PACKAGE)
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java
new file mode 100644
index 0000000..1986596
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java
@@ -0,0 +1,117 @@
+package com.android.providers.settings;
+
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.test.AndroidTestCase;
+
+import java.util.Random;
+
+/**
+ * Tests for NetworkPolicySerializer
+ */
+public class NetworkPolicySerializerTest extends AndroidTestCase {
+ static Random sRandom = new Random();
+
+ public void testMarshallAndUnmarshalNetworkPolicy() {
+ NetworkPolicy policy = getDummyNetworkPolicy();
+ byte[] data = NetworkPolicySerializer.marshalNetworkPolicy(policy);
+ assertNotNull("Got Null data from marshal", data);
+ assertFalse("Got back an empty byte[] from marshal", data.length == 0);
+
+ NetworkPolicy unmarshaled = NetworkPolicySerializer.unmarshalNetworkPolicy(data);
+ assertNotNull("Got Null data from unmarshaled", unmarshaled);
+ assertTrue("NetworkPolicy Marshall and Unmarshal Failed!", policy.equals(unmarshaled));
+ }
+
+ public void testMarshallNetworkPolicyEdgeCases() {
+ byte[] data = NetworkPolicySerializer.marshalNetworkPolicy(null);
+ assertNotNull("NetworkPolicy marshal returned null. Expected: byte[0]", data);
+ assertEquals("NetworkPolicy marshal returned incomplete byte array. Expected: byte[0]",
+ data.length, 0);
+ }
+
+ public void testUnmarshallNetworkPolicyEdgeCases() {
+ NetworkPolicy policy = NetworkPolicySerializer.unmarshalNetworkPolicy(null);
+ assertNull("Non null NetworkPolicy returned for null byte[] input", policy);
+
+ policy = NetworkPolicySerializer.unmarshalNetworkPolicy(new byte[0]);
+ assertNull("Non null NetworkPolicy returned for empty byte[] input", policy);
+
+ policy = NetworkPolicySerializer.unmarshalNetworkPolicy(new byte[]{10, 20, 30, 40, 50, 60});
+ assertNull("Non null NetworkPolicy returned for incomplete byte[] input", policy);
+ }
+
+ public void testMarshallAndUnmarshalNetworkPolicies() {
+ NetworkPolicy[] policies = getDummyNetworkPolicies(5);
+ byte[] data = NetworkPolicySerializer.marshalNetworkPolicies(policies);
+ assertNotNull("Got Null data from marshal", data);
+ assertFalse("Got back an empty byte[] from marshal", data.length == 0);
+
+ NetworkPolicy[] unmarshaled = NetworkPolicySerializer.unmarshalNetworkPolicies(data);
+ assertNotNull("Got Null data from unmarshaled", unmarshaled);
+ try {
+ for (int i = 0; i < policies.length; i++) {
+ assertTrue("NetworkPolicies Marshall and Unmarshal Failed!",
+ policies[i].equals(unmarshaled[i]));
+ }
+ } catch (NullPointerException npe) {
+ assertTrue("Some policies were not marshaled/unmarshaled correctly", false);
+ }
+ }
+
+ public void testMarshallNetworkPoliciesEdgeCases() {
+ byte[] data = NetworkPolicySerializer.marshalNetworkPolicies(null);
+ assertNotNull("NetworkPolicies marshal returned null!", data);
+ assertEquals("NetworkPolicies marshal returned incomplete byte array", data.length, 0);
+
+ data = NetworkPolicySerializer.marshalNetworkPolicies(new NetworkPolicy[0]);
+ assertNotNull("NetworkPolicies marshal returned null for empty NetworkPolicy[]", data);
+ assertEquals("NetworkPolicies marshal returned incomplete byte array for empty NetworkPolicy[]"
+ , data.length, 0);
+ }
+
+ public void testUnmarshalNetworkPoliciesEdgeCases() {
+ NetworkPolicy[] policies = NetworkPolicySerializer.unmarshalNetworkPolicies(null);
+ assertNotNull("NetworkPolicies unmarshal returned null for null input. Expected: byte[0] ",
+ policies);
+ assertEquals("Non Empty NetworkPolicy[] returned for null input Expected: byte[0]",
+ policies.length, 0);
+
+ policies = NetworkPolicySerializer.unmarshalNetworkPolicies(new byte[0]);
+ assertNotNull("NetworkPolicies unmarshal returned null for empty byte[] input. Expected: byte[0]",
+ policies);
+ assertEquals("Non Empty NetworkPolicy[] returned for empty byte[] input. Expected: byte[0]",
+ policies.length, 0);
+
+ policies = NetworkPolicySerializer.unmarshalNetworkPolicies(new byte[]{10, 20, 30, 40, 50, 60});
+ assertNotNull("NetworkPolicies unmarshal returned null for incomplete byte[] input. " +
+ "Expected: byte[0] ", policies);
+ assertEquals("Non Empty NetworkPolicy[] returned for incomplete byte[] input Expected: byte[0]",
+ policies.length, 0);
+
+ }
+
+ private NetworkPolicy[] getDummyNetworkPolicies(int num) {
+ NetworkPolicy[] policies = new NetworkPolicy[num];
+ for (int i = 0; i < num; i++) {
+ policies[i] = getDummyNetworkPolicy();
+ }
+ return policies;
+ }
+
+ private NetworkPolicy getDummyNetworkPolicy() {
+ NetworkTemplate template = new NetworkTemplate(NetworkTemplate.MATCH_MOBILE_ALL, "subId",
+ "GoogleGuest");
+ int cycleDay = sRandom.nextInt();
+ String cycleTimezone = "timezone";
+ long warningBytes = sRandom.nextLong();
+ long limitBytes = sRandom.nextLong();
+ long lastWarningSnooze = sRandom.nextLong();
+ long lastLimitSnooze = sRandom.nextLong();
+ boolean metered = sRandom.nextInt() % 2 == 0;
+ boolean inferred = sRandom.nextInt() % 2 == 0;
+ return new NetworkPolicy(template, cycleDay, cycleTimezone, warningBytes, limitBytes,
+ lastWarningSnooze, lastLimitSnooze, metered, inferred);
+ }
+
+}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 44018a0..e89ab70 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -53,6 +53,7 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener;
@@ -73,6 +74,7 @@
* <p>
* <strong>NOTE</strong>: these tests only work if the device is unlocked.
*/
+@LargeTest
public class BugreportReceiverTest extends InstrumentationTestCase {
private static final String TAG = "BugreportReceiverTest";
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2e65656..51b84f5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -136,6 +136,9 @@
android:protectionLevel="signature" />
<uses-permission android:name="com.android.systemui.permission.SELF" />
+ <!-- Adding Quick Settings tiles -->
+ <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index e949adc..a995ec7 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -78,47 +78,64 @@
android:tint="@android:color/white" />
</LinearLayout>
- <include layout="@layout/split_clock_view"
- android:layout_width="wrap_content"
+ <TextView
+ android:id="@+id/header_emergency_calls_only"
android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_marginTop="2dp"
+ android:layout_width="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
- android:id="@+id/clock"
- />
-
- <com.android.systemui.statusbar.policy.DateView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="6dp"
- android:layout_marginTop="8dp"
- android:layout_toEndOf="@id/clock"
- android:layout_alignParentTop="true"
- android:drawableStart="@drawable/header_dot"
- android:drawablePadding="6dp"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
- android:textSize="@dimen/qs_time_collapsed_size"
- systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm"
- />
-
- <com.android.systemui.statusbar.AlphaOptimizedButton
- android:id="@+id/alarm_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_toEndOf="@id/date"
- android:drawablePadding="6dp"
- android:drawableStart="@drawable/ic_access_alarms_small"
- android:textColor="#64ffffff"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
- android:minHeight="36dp"
- android:paddingStart="6dp"
- android:background="?android:attr/selectableItemBackground"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:paddingTop="8dp"
android:visibility="gone"
- />
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
+ android:text="@*android:string/emergency_calls_only"
+ android:singleLine="true"
+ android:gravity="center_vertical" />
+
+ <LinearLayout
+ android:id="@+id/date_time_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:orientation="horizontal">
+
+ <include layout="@layout/split_clock_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="2dp"
+ android:id="@+id/clock" />
+
+ <com.android.systemui.statusbar.policy.DateView
+ android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="6dp"
+ android:layout_marginTop="8dp"
+ android:layout_alignParentTop="true"
+ android:drawableStart="@drawable/header_dot"
+ android:drawablePadding="6dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ android:textSize="@dimen/qs_time_collapsed_size"
+ systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+
+ <com.android.systemui.statusbar.AlphaOptimizedButton
+ android:id="@+id/alarm_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:drawablePadding="6dp"
+ android:drawableStart="@drawable/ic_access_alarms_small"
+ android:textColor="#64ffffff"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
+ android:minHeight="36dp"
+ android:paddingStart="6dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:visibility="gone" />
+ </LinearLayout>
<com.android.systemui.qs.QuickQSPanel
android:id="@+id/quick_qs_panel"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 388da17..40e8b50 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -103,7 +103,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,bt,inversion,dnd,cell,airplane,rotation,flashlight,location,cast,hotspot
+ wifi,bt,flashlight,dnd,cell,battery,rotation,airplane,location,cast
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8737d43..4f070d6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -136,6 +136,7 @@
<dimen name="qs_quick_actions_padding">25dp</dimen>
<dimen name="qs_quick_tile_size">48dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
+ <dimen name="qs_date_anim_translation">44.5dp</dimen>
<dimen name="qs_page_indicator_size">12dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
<dimen name="qs_tile_text_size">12sp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index b1847e1..a781585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1299,7 +1299,6 @@
// Since the number of notifications is determined based on the height of the view, we
// need to update them.
updateRowStates();
- mStackScroller.onHeightChanged(null, false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 2b93554..bd143ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -688,6 +688,9 @@
mOnKeyguard = onKeyguard;
logExpansionEvent(false, wasExpanded);
if (wasExpanded != isExpanded()) {
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.updateGroupOverflow();
+ }
notifyHeightChanged(false /* needsAnimation */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index c4930a9..1372cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -62,11 +62,15 @@
private boolean mDetailTransitioning;
private ViewGroup mExpandedGroup;
+ private ViewGroup mDateTimeGroup;
+ private View mEmergencyOnly;
private TextView mQsDetailHeaderTitle;
private boolean mListening;
private AlarmManager.AlarmClockInfo mNextAlarm;
private QuickQSPanel mHeaderQsPanel;
+ private boolean mShowEmergencyCallsOnly;
+ private float mDateTimeTranslation;
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -76,6 +80,10 @@
protected void onFinishInflate() {
super.onFinishInflate();
+ mEmergencyOnly = findViewById(R.id.header_emergency_calls_only);
+ mDateTimeTranslation = mContext.getResources().getDimension(
+ R.dimen.qs_date_anim_translation);
+ mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
@@ -141,6 +149,9 @@
mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE);
mHeaderQsPanel.setAlpha(1 - headerExpansionFraction);
mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE);
+
+ mDateTimeGroup.setTranslationY(headerExpansionFraction * mDateTimeTranslation);
+ mEmergencyOnly.setAlpha(headerExpansionFraction);
}
public void setListening(boolean listening) {
@@ -160,6 +171,8 @@
private void updateVisibilities() {
mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
mQsDetailHeader.setVisibility(mExpanded && mShowingDetail ? View.VISIBLE : View.INVISIBLE);
+ mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly
+ ? View.VISIBLE : View.INVISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
}
@@ -256,8 +269,14 @@
}
@Override
- public void setEmergencyCallsOnly(boolean emergencyOnly) {
- // Don't care.
+ public void setEmergencyCallsOnly(boolean show) {
+ boolean changed = show != mShowEmergencyCallsOnly;
+ if (changed) {
+ mShowEmergencyCallsOnly = show;
+ if (mExpanded) {
+ updateEverything();
+ }
+ }
}
private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
@@ -314,6 +333,7 @@
private void handleShowingDetail(final QSTile.DetailAdapter detail) {
final boolean showingDetail = detail != null;
+ transition(mDateTimeGroup, !showingDetail);
transition(mExpandedGroup, !showingDetail);
if (mAlarmShowing) {
transition(mAlarmStatus, !showingDetail);
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index ede92fb..7fcedc6 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -788,6 +788,9 @@
@Override
public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
+ if (callback == null) {
+ return;
+ }
synchronized (this) {
op = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
Callback cb = mModeWatchers.get(callback.asBinder());
@@ -816,6 +819,9 @@
@Override
public void stopWatchingMode(IAppOpsCallback callback) {
+ if (callback == null) {
+ return;
+ }
synchronized (this) {
Callback cb = mModeWatchers.remove(callback.asBinder());
if (cb != null) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index c712a56..ed64c2b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -34,6 +34,7 @@
import android.annotation.Nullable;
import android.app.AlarmManager;
+import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -72,6 +73,7 @@
import android.net.UidRange;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.FileUtils;
import android.os.Handler;
@@ -1529,6 +1531,7 @@
log("sendStickyBroadcast: action=" + intent.getAction());
}
+ Bundle options = null;
final long ident = Binder.clearCallingIdentity();
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
final NetworkInfo ni = intent.getParcelableExtra(
@@ -1536,6 +1539,10 @@
if (ni.getType() == ConnectivityManager.TYPE_MOBILE_SUPL) {
intent.setAction(ConnectivityManager.CONNECTIVITY_ACTION_SUPL);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ } else {
+ BroadcastOptions opts = BroadcastOptions.makeBasic();
+ opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M);
+ options = opts.toBundle();
}
final IBatteryStats bs = BatteryStatsService.getService();
try {
@@ -1546,7 +1553,7 @@
}
}
try {
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL, options);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 45c1ed2..0282a72 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -3536,6 +3536,7 @@
private static final String ATTR_LABEL = "label";
private static final String ATTR_ICON = "icon";
private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
+ private static final String ATTR_IME_SUBTYPE_LANGUAGE_TAG = "languageTag";
private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
@@ -3629,6 +3630,8 @@
out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
+ out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
+ subtype.getLanguageTag());
out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
out.attribute(null, ATTR_IS_AUXILIARY,
@@ -3690,6 +3693,8 @@
parser.getAttributeValue(null, ATTR_LABEL));
final String imeSubtypeLocale =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
+ final String languageTag =
+ parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG);
final String imeSubtypeMode =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
final String imeSubtypeExtraValue =
@@ -3700,6 +3705,7 @@
.setSubtypeNameResId(label)
.setSubtypeIconResId(icon)
.setSubtypeLocale(imeSubtypeLocale)
+ .setLanguageTag(languageTag)
.setSubtypeMode(imeSubtypeMode)
.setSubtypeExtraValue(imeSubtypeExtraValue)
.setIsAuxiliary(isAuxiliary)
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 1bab7b9..622aa16 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -468,7 +468,7 @@
}
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
- BroadcastFilter filter, boolean ordered) {
+ BroadcastFilter filter, boolean ordered, int index) {
boolean skip = false;
if (filter.requiredPermission != null) {
int perm = mService.checkComponentPermission(filter.requiredPermission,
@@ -576,64 +576,70 @@
if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
r.callingPid, r.resolvedType, filter.receiverList.uid)) {
- return;
+ skip = true;
}
- if (filter.receiverList.app == null || filter.receiverList.app.crashing) {
+ if (!skip && (filter.receiverList.app == null || filter.receiverList.app.crashing)) {
Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
+ " to " + filter.receiverList + ": process crashing");
skip = true;
}
- if (!skip) {
- // If permissions need a review before any of the app components can run, we drop
- // the broadcast and if the calling app is in the foreground and the broadcast is
- // explicit we launch the review UI passing it a pending intent to send the skipped
- // broadcast.
- if (Build.PERMISSIONS_REVIEW_REQUIRED) {
- if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
- filter.owningUserId)) {
- return;
- }
- }
+ if (skip) {
+ r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
+ return;
+ }
- // If this is not being sent as an ordered broadcast, then we
- // don't want to touch the fields that keep track of the current
- // state of ordered broadcasts.
- if (ordered) {
- r.receiver = filter.receiverList.receiver.asBinder();
- r.curFilter = filter;
- filter.receiverList.curBroadcast = r;
- r.state = BroadcastRecord.CALL_IN_RECEIVE;
- if (filter.receiverList.app != null) {
- // Bump hosting application to no longer be in background
- // scheduling class. Note that we can't do that if there
- // isn't an app... but we can only be in that case for
- // things that directly call the IActivityManager API, which
- // are already core system stuff so don't matter for this.
- r.curApp = filter.receiverList.app;
- filter.receiverList.app.curReceiver = r;
- mService.updateOomAdjLocked(r.curApp);
- }
+ // If permissions need a review before any of the app components can run, we drop
+ // the broadcast and if the calling app is in the foreground and the broadcast is
+ // explicit we launch the review UI passing it a pending intent to send the skipped
+ // broadcast.
+ if (Build.PERMISSIONS_REVIEW_REQUIRED) {
+ if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
+ filter.owningUserId)) {
+ r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
+ return;
}
- try {
- if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
- "Delivering to " + filter + " : " + r);
- performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
- new Intent(r.intent), r.resultCode, r.resultData,
- r.resultExtras, r.ordered, r.initialSticky, r.userId);
- if (ordered) {
- r.state = BroadcastRecord.CALL_DONE_RECEIVE;
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
- if (ordered) {
- r.receiver = null;
- r.curFilter = null;
- filter.receiverList.curBroadcast = null;
- if (filter.receiverList.app != null) {
- filter.receiverList.app.curReceiver = null;
- }
+ }
+
+ r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
+
+ // If this is not being sent as an ordered broadcast, then we
+ // don't want to touch the fields that keep track of the current
+ // state of ordered broadcasts.
+ if (ordered) {
+ r.receiver = filter.receiverList.receiver.asBinder();
+ r.curFilter = filter;
+ filter.receiverList.curBroadcast = r;
+ r.state = BroadcastRecord.CALL_IN_RECEIVE;
+ if (filter.receiverList.app != null) {
+ // Bump hosting application to no longer be in background
+ // scheduling class. Note that we can't do that if there
+ // isn't an app... but we can only be in that case for
+ // things that directly call the IActivityManager API, which
+ // are already core system stuff so don't matter for this.
+ r.curApp = filter.receiverList.app;
+ filter.receiverList.app.curReceiver = r;
+ mService.updateOomAdjLocked(r.curApp);
+ }
+ }
+ try {
+ if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
+ "Delivering to " + filter + " : " + r);
+ performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
+ new Intent(r.intent), r.resultCode, r.resultData,
+ r.resultExtras, r.ordered, r.initialSticky, r.userId);
+ if (ordered) {
+ r.state = BroadcastRecord.CALL_DONE_RECEIVE;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
+ if (ordered) {
+ r.receiver = null;
+ r.curFilter = null;
+ filter.receiverList.curBroadcast = null;
+ if (filter.receiverList.app != null) {
+ filter.receiverList.app.curReceiver = null;
}
}
}
@@ -740,7 +746,7 @@
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Delivering non-ordered on [" + mQueueName + "] to registered "
+ target + ": " + r);
- deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
+ deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);
}
addBroadcastToHistoryLocked(r);
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
@@ -897,7 +903,7 @@
"Delivering ordered ["
+ mQueueName + "] to registered "
+ filter + ": " + r);
- deliverToRegisteredReceiverLocked(r, filter, r.ordered);
+ deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
if (r.receiver == null || !r.ordered) {
// The receiver has already finished, so schedule to
// process the next one.
@@ -925,10 +931,17 @@
info.activityInfo.name);
boolean skip = false;
+ if (brOptions != null &&
+ (info.activityInfo.applicationInfo.targetSdkVersion
+ < brOptions.getMinManifestReceiverApiLevel() ||
+ info.activityInfo.applicationInfo.targetSdkVersion
+ > brOptions.getMaxManifestReceiverApiLevel())) {
+ skip = true;
+ }
int perm = mService.checkComponentPermission(info.activityInfo.permission,
r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
info.activityInfo.exported);
- if (perm != PackageManager.PERMISSION_GRANTED) {
+ if (!skip && perm != PackageManager.PERMISSION_GRANTED) {
if (!info.activityInfo.exported) {
Slog.w(TAG, "Permission Denial: broadcasting "
+ r.intent.toString()
@@ -945,7 +958,7 @@
+ " due to receiver " + component.flattenToShortString());
}
skip = true;
- } else if (info.activityInfo.permission != null) {
+ } else if (!skip && info.activityInfo.permission != null) {
final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
if (opCode != AppOpsManager.OP_NONE
&& mService.mAppOpsService.noteOperation(opCode, r.callingUid,
@@ -1118,6 +1131,7 @@
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
+ r + " for whatever reason");
+ r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
r.receiver = null;
r.curFilter = null;
r.state = BroadcastRecord.IDLE;
@@ -1125,6 +1139,7 @@
return;
}
+ r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
r.state = BroadcastRecord.APP_RECEIVE;
r.curComponent = component;
r.curReceiver = info.activityInfo;
@@ -1294,6 +1309,7 @@
String anrMessage = null;
Object curReceiver = r.receivers.get(r.nextReceiver-1);
+ r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
Slog.w(TAG, "Receiver during timeout: " + curReceiver);
logBroadcastReceiverDiscardLocked(r);
if (curReceiver instanceof BroadcastFilter) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 1a269cf..e99cbf9 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -57,6 +57,7 @@
final int appOp; // an app op that is associated with this broadcast
final BroadcastOptions options; // BroadcastOptions supplied by caller
final List receivers; // contains BroadcastFilter and ResolveInfo
+ final int[] delivery; // delivery state of each receiver
IIntentReceiver resultTo; // who receives final result if non-null
long enqueueClockTime; // the clock time the broadcast was enqueued
long dispatchTime; // when dispatch started on this set of receivers
@@ -79,6 +80,11 @@
static final int CALL_DONE_RECEIVE = 3;
static final int WAITING_SERVICES = 4;
+ static final int DELIVERY_PENDING = 0;
+ static final int DELIVERY_DELIVERED = 1;
+ static final int DELIVERY_SKIPPED = 2;
+ static final int DELIVERY_TIMEOUT = 3;
+
// The following are set when we are calling a receiver (one that
// was found in our list of registered receivers).
BroadcastFilter curFilter;
@@ -183,12 +189,24 @@
PrintWriterPrinter printer = new PrintWriterPrinter(pw);
for (int i = 0; i < N; i++) {
Object o = receivers.get(i);
- pw.print(prefix); pw.print("Receiver #"); pw.print(i);
- pw.print(": "); pw.println(o);
- if (o instanceof BroadcastFilter)
- ((BroadcastFilter)o).dumpBrief(pw, p2);
- else if (o instanceof ResolveInfo)
- ((ResolveInfo)o).dump(printer, p2, 0);
+ pw.print(prefix);
+ switch (delivery[i]) {
+ case DELIVERY_PENDING: pw.print("Pending"); break;
+ case DELIVERY_DELIVERED: pw.print("Deliver"); break;
+ case DELIVERY_SKIPPED: pw.print("Skipped"); break;
+ case DELIVERY_TIMEOUT: pw.print("Timeout"); break;
+ default: pw.print("???????"); break;
+ }
+ pw.print(" #"); pw.print(i); pw.print(": ");
+ if (o instanceof BroadcastFilter) {
+ pw.println(o);
+ ((BroadcastFilter) o).dumpBrief(pw, p2);
+ } else if (o instanceof ResolveInfo) {
+ pw.println("(manifest)");
+ ((ResolveInfo) o).dump(printer, p2, 0);
+ } else {
+ pw.println(o);
+ }
}
}
@@ -211,6 +229,7 @@
appOp = _appOp;
options = _options;
receivers = _receivers;
+ delivery = new int[_receivers != null ? _receivers.size() : 0];
resultTo = _resultTo;
resultCode = _resultCode;
resultData = _resultData;
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index a066835..78618ce 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -345,25 +345,9 @@
}
public void updateRunningAccounts() {
- mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
-
- if (mBootCompleted) {
- doDatabaseCleanup();
- }
-
- AccountAndUser[] accounts = mRunningAccounts;
- for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
- if (!containsAccountAndUser(accounts,
- currentSyncContext.mSyncOperation.target.account,
- currentSyncContext.mSyncOperation.target.userId)) {
- Log.d(TAG, "canceling sync since the account is no longer running");
- sendSyncFinishedOrCanceledMessage(currentSyncContext,
- null /* no result since this is a cancel */);
- }
- }
- // we must do this since we don't bother scheduling alarms when
- // the accounts are not set yet
- sendCheckAlarmsMessage();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_ACCOUNTS_UPDATED");
+ // Update accounts in handler thread.
+ mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_ACCOUNTS_UPDATED);
}
private void doDatabaseCleanup() {
@@ -2179,6 +2163,7 @@
* obj: {@link com.android.server.content.SyncManager.ActiveSyncContext}
*/
private static final int MESSAGE_MONITOR_SYNC = 8;
+ private static final int MESSAGE_ACCOUNTS_UPDATED = 9;
public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
private Long mAlarmScheduleTime = null;
@@ -2296,6 +2281,13 @@
// to also take into account the periodic syncs.
earliestFuturePollTime = scheduleReadyPeriodicSyncs();
switch (msg.what) {
+ case SyncHandler.MESSAGE_ACCOUNTS_UPDATED:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_ACCOUNTS_UPDATED");
+ }
+ updateRunningAccountsH();
+ break;
+
case SyncHandler.MESSAGE_CANCEL:
SyncStorageEngine.EndPoint endpoint = (SyncStorageEngine.EndPoint) msg.obj;
Bundle extras = msg.peekData();
@@ -2872,7 +2864,28 @@
mLocalDeviceIdleController.setSyncActive(active);
}
}
+ }
+ private void updateRunningAccountsH() {
+ mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+
+ if (mBootCompleted) {
+ doDatabaseCleanup();
+ }
+
+ AccountAndUser[] accounts = mRunningAccounts;
+ for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+ if (!containsAccountAndUser(accounts,
+ currentSyncContext.mSyncOperation.target.account,
+ currentSyncContext.mSyncOperation.target.userId)) {
+ Log.d(TAG, "canceling sync since the account is no longer running");
+ sendSyncFinishedOrCanceledMessage(currentSyncContext,
+ null /* no result since this is a cancel */);
+ }
+ }
+ // we must do this since we don't bother scheduling alarms when
+ // the accounts are not set yet
+ sendCheckAlarmsMessage();
}
private boolean isSyncNotUsingNetworkH(ActiveSyncContext activeSyncContext) {
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index b3d7287..33b09e3 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -17,11 +17,8 @@
package com.android.server.job.controllers;
import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
+import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.SystemClock;
import android.util.Slog;
@@ -40,15 +37,11 @@
*/
public class TimeController extends StateController {
private static final String TAG = "JobScheduler.Time";
- private static final String ACTION_JOB_EXPIRED =
- "android.content.jobscheduler.JOB_DEADLINE_EXPIRED";
- private static final String ACTION_JOB_DELAY_EXPIRED =
- "android.content.jobscheduler.JOB_DELAY_EXPIRED";
- /** Set an alarm for the next job expiry. */
- private final PendingIntent mDeadlineExpiredAlarmIntent;
- /** Set an alarm for the next job delay expiry. This*/
- private final PendingIntent mNextDelayExpiredAlarmIntent;
+ /** Deadline alarm tag for logging purposes */
+ private final String DEADLINE_TAG = "deadline";
+ /** Delay alarm tag for logging purposes */
+ private final String DELAY_TAG = "delay";
private long mNextJobExpiredElapsedMillis;
private long mNextDelayExpiredElapsedMillis;
@@ -68,19 +61,9 @@
private TimeController(StateChangedListener stateChangedListener, Context context) {
super(stateChangedListener, context);
- mDeadlineExpiredAlarmIntent =
- PendingIntent.getBroadcast(mContext, 0 /* ignored */,
- new Intent(ACTION_JOB_EXPIRED), 0);
- mNextDelayExpiredAlarmIntent =
- PendingIntent.getBroadcast(mContext, 0 /* ignored */,
- new Intent(ACTION_JOB_DELAY_EXPIRED), 0);
+
mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
-
- // Register BR for these intents.
- IntentFilter intentFilter = new IntentFilter(ACTION_JOB_EXPIRED);
- intentFilter.addAction(ACTION_JOB_DELAY_EXPIRED);
- mContext.registerReceiver(mAlarmExpiredReceiver, intentFilter);
}
/**
@@ -224,7 +207,7 @@
private void setDelayExpiredAlarm(long alarmTimeElapsedMillis) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
- updateAlarmWithPendingIntent(mNextDelayExpiredAlarmIntent, mNextDelayExpiredElapsedMillis);
+ updateAlarmWithListener(DELAY_TAG, mNextDelayExpiredListener, mNextDelayExpiredElapsedMillis);
}
/**
@@ -235,7 +218,7 @@
private void setDeadlineExpiredAlarm(long alarmTimeElapsedMillis) {
alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
- updateAlarmWithPendingIntent(mDeadlineExpiredAlarmIntent, mNextJobExpiredElapsedMillis);
+ updateAlarmWithListener(DEADLINE_TAG, mDeadlineExpiredListener, mNextJobExpiredElapsedMillis);
}
private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
@@ -246,31 +229,39 @@
return proposedAlarmTimeElapsedMillis;
}
- private void updateAlarmWithPendingIntent(PendingIntent pi, long alarmTimeElapsed) {
+ private void updateAlarmWithListener(String tag, OnAlarmListener listener,
+ long alarmTimeElapsed) {
ensureAlarmService();
if (alarmTimeElapsed == Long.MAX_VALUE) {
- mAlarmService.cancel(pi);
+ mAlarmService.cancel(listener);
} else {
if (DEBUG) {
- Slog.d(TAG, "Setting " + pi.getIntent().getAction() + " for: " + alarmTimeElapsed);
+ Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
}
- mAlarmService.set(AlarmManager.ELAPSED_REALTIME, alarmTimeElapsed, pi);
+ mAlarmService.set(AlarmManager.ELAPSED_REALTIME, alarmTimeElapsed,
+ tag, listener, null);
}
}
- private final BroadcastReceiver mAlarmExpiredReceiver = new BroadcastReceiver() {
+ // Job/delay expiration alarm handling
+
+ private final OnAlarmListener mDeadlineExpiredListener = new OnAlarmListener() {
@Override
- public void onReceive(Context context, Intent intent) {
+ public void onAlarm() {
if (DEBUG) {
- Slog.d(TAG, "Just received alarm: " + intent.getAction());
+ Slog.d(TAG, "Deadline-expired alarm fired");
}
- // A job has just expired, so we run through the list of jobs that we have and
- // notify our StateChangedListener.
- if (ACTION_JOB_EXPIRED.equals(intent.getAction())) {
- checkExpiredDeadlinesAndResetAlarm();
- } else if (ACTION_JOB_DELAY_EXPIRED.equals(intent.getAction())) {
- checkExpiredDelaysAndResetAlarm();
+ checkExpiredDeadlinesAndResetAlarm();
+ }
+ };
+
+ private final OnAlarmListener mNextDelayExpiredListener = new OnAlarmListener() {
+ @Override
+ public void onAlarm() {
+ if (DEBUG) {
+ Slog.d(TAG, "Delay-expired alarm fired");
}
+ checkExpiredDelaysAndResetAlarm();
}
};
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 812d9b6..2329b42 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -389,7 +389,6 @@
}
private void scheduleRenew() {
- mRenewAlarm.cancel();
if (mDhcpLeaseExpiry != 0) {
long now = SystemClock.elapsedRealtime();
long alarmTime = (now + mDhcpLeaseExpiry) / 2;
@@ -822,6 +821,11 @@
return NOT_HANDLED;
}
}
+
+ @Override
+ public void exit() {
+ mRenewAlarm.cancel();
+ }
}
class DhcpRenewingState extends PacketRetransmittingState {
diff --git a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
index 757f1c6..13657ab 100644
--- a/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
+++ b/services/tests/servicestests/src/com/android/server/BroadcastInterceptingContext.java
@@ -21,6 +21,7 @@
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
@@ -165,6 +166,11 @@
}
@Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
+ sendBroadcast(intent);
+ }
+
+ @Override
public void removeStickyBroadcast(Intent intent) {
// ignored
}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 96c8185..64d2978 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -433,6 +433,12 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
+ @Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 21f47bc2..0bb88a7 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -383,6 +383,16 @@
}
}
+static void printUsesPermissionSdk23(const String8& name, int maxSdkVersion=-1) {
+ printf("uses-permission-sdk-23: ");
+
+ printf("name='%s'", ResTable::normalizeForOutput(name.string()).string());
+ if (maxSdkVersion != -1) {
+ printf(" maxSdkVersion='%d'", maxSdkVersion);
+ }
+ printf("\n");
+}
+
static void printUsesImpliedPermission(const String8& name, const String8& reason) {
printf("uses-implied-permission: name='%s' reason='%s'\n",
ResTable::normalizeForOutput(name.string()).string(),
@@ -463,12 +473,20 @@
* a pre-requisite or some other reason.
*/
struct ImpliedFeature {
+ ImpliedFeature() : impliedBySdk23(false) {}
+ ImpliedFeature(const String8& n, bool sdk23) : name(n), impliedBySdk23(sdk23) {}
+
/**
* Name of the implied feature.
*/
String8 name;
/**
+ * Was this implied by a permission from SDK 23 (<uses-permission-sdk-23 />)?
+ */
+ bool impliedBySdk23;
+
+ /**
* List of human-readable reasons for why this feature was implied.
*/
SortedVector<String8> reasons;
@@ -497,18 +515,24 @@
};
static void addImpliedFeature(KeyedVector<String8, ImpliedFeature>* impliedFeatures,
- const char* name, const char* reason) {
+ const char* name, const char* reason, bool sdk23) {
String8 name8(name);
ssize_t idx = impliedFeatures->indexOfKey(name8);
if (idx < 0) {
- idx = impliedFeatures->add(name8, ImpliedFeature());
- impliedFeatures->editValueAt(idx).name = name8;
+ idx = impliedFeatures->add(name8, ImpliedFeature(name8, sdk23));
}
- impliedFeatures->editValueAt(idx).reasons.add(String8(reason));
+
+ ImpliedFeature* feature = &impliedFeatures->editValueAt(idx);
+
+ // A non-sdk 23 implied feature takes precedence.
+ if (feature->impliedBySdk23 && !sdk23) {
+ feature->impliedBySdk23 = false;
+ }
+ feature->reasons.add(String8(reason));
}
-static void printFeatureGroup(const FeatureGroup& grp,
- const KeyedVector<String8, ImpliedFeature>* impliedFeatures = NULL) {
+static void printFeatureGroupImpl(const FeatureGroup& grp,
+ const KeyedVector<String8, ImpliedFeature>* impliedFeatures) {
printf("feature-group: label='%s'\n", grp.label.string());
if (grp.openGLESVersion > 0) {
@@ -536,9 +560,11 @@
String8 printableFeatureName(ResTable::normalizeForOutput(
impliedFeature.name.string()));
- printf(" uses-feature: name='%s'\n", printableFeatureName.string());
- printf(" uses-implied-feature: name='%s' reason='",
- printableFeatureName.string());
+ const char* sdk23Suffix = impliedFeature.impliedBySdk23 ? "-sdk-23" : "";
+
+ printf(" uses-feature%s: name='%s'\n", sdk23Suffix, printableFeatureName.string());
+ printf(" uses-implied-feature%s: name='%s' reason='", sdk23Suffix,
+ printableFeatureName.string());
const size_t numReasons = impliedFeature.reasons.size();
for (size_t j = 0; j < numReasons; j++) {
printf("%s", impliedFeature.reasons[j].string());
@@ -552,6 +578,15 @@
}
}
+static void printFeatureGroup(const FeatureGroup& grp) {
+ printFeatureGroupImpl(grp, NULL);
+}
+
+static void printDefaultFeatureGroup(const FeatureGroup& grp,
+ const KeyedVector<String8, ImpliedFeature>& impliedFeatures) {
+ printFeatureGroupImpl(grp, &impliedFeatures);
+}
+
static void addParentFeatures(FeatureGroup* grp, const String8& name) {
if (name == "android.hardware.camera.autofocus" ||
name == "android.hardware.camera.flash") {
@@ -572,6 +607,72 @@
}
}
+static void addImpliedFeaturesForPermission(const int targetSdk, const String8& name,
+ KeyedVector<String8, ImpliedFeature>* impliedFeatures,
+ bool impliedBySdk23Permission) {
+ if (name == "android.permission.CAMERA") {
+ addImpliedFeature(impliedFeatures, "android.hardware.camera",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
+ addImpliedFeature(impliedFeatures, "android.hardware.location.gps",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ addImpliedFeature(impliedFeatures, "android.hardware.location",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
+ addImpliedFeature(impliedFeatures, "android.hardware.location",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
+ addImpliedFeature(impliedFeatures, "android.hardware.location.network",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ addImpliedFeature(impliedFeatures, "android.hardware.location",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
+ name == "android.permission.INSTALL_LOCATION_PROVIDER") {
+ addImpliedFeature(impliedFeatures, "android.hardware.location",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.BLUETOOTH" ||
+ name == "android.permission.BLUETOOTH_ADMIN") {
+ if (targetSdk > 4) {
+ addImpliedFeature(impliedFeatures, "android.hardware.bluetooth",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ addImpliedFeature(impliedFeatures, "android.hardware.bluetooth",
+ "targetSdkVersion > 4", impliedBySdk23Permission);
+ }
+ } else if (name == "android.permission.RECORD_AUDIO") {
+ addImpliedFeature(impliedFeatures, "android.hardware.microphone",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
+ name == "android.permission.CHANGE_WIFI_STATE" ||
+ name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
+ addImpliedFeature(impliedFeatures, "android.hardware.wifi",
+ String8::format("requested %s permission", name.string())
+ .string(), impliedBySdk23Permission);
+ } else if (name == "android.permission.CALL_PHONE" ||
+ name == "android.permission.CALL_PRIVILEGED" ||
+ name == "android.permission.MODIFY_PHONE_STATE" ||
+ name == "android.permission.PROCESS_OUTGOING_CALLS" ||
+ name == "android.permission.READ_SMS" ||
+ name == "android.permission.RECEIVE_SMS" ||
+ name == "android.permission.RECEIVE_MMS" ||
+ name == "android.permission.RECEIVE_WAP_PUSH" ||
+ name == "android.permission.SEND_SMS" ||
+ name == "android.permission.WRITE_APN_SETTINGS" ||
+ name == "android.permission.WRITE_SMS") {
+ addImpliedFeature(impliedFeatures, "android.hardware.telephony",
+ String8("requested a telephony permission").string(),
+ impliedBySdk23Permission);
+ }
+}
+
/*
* Handle the "dump" command, to extract select data from an archive.
*/
@@ -712,7 +813,8 @@
size_t len;
ResXMLTree::event_code_t code;
int depth = 0;
- while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT &&
+ code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::END_TAG) {
depth--;
continue;
@@ -735,25 +837,53 @@
}
String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL);
printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string());
- } else if (depth == 2 && tag == "permission") {
- String8 error;
- String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
- if (error != "") {
- fprintf(stderr, "ERROR: %s\n", error.string());
- goto bail;
+ } else if (depth == 2) {
+ if (tag == "permission") {
+ String8 error;
+ String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for permission\n");
+ goto bail;
+ }
+ printf("permission: %s\n",
+ ResTable::normalizeForOutput(name.string()).string());
+ } else if (tag == "uses-permission") {
+ String8 error;
+ String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n");
+ goto bail;
+ }
+ printUsesPermission(name,
+ AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
+ AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
+ } else if (tag == "uses-permission-sdk-23" || tag == "uses-permission-sdk-m") {
+ String8 error;
+ String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR: %s\n", error.string());
+ goto bail;
+ }
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for "
+ "uses-permission-sdk-23\n");
+ goto bail;
+ }
+ printUsesPermissionSdk23(
+ name,
+ AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
}
- printf("permission: %s\n",
- ResTable::normalizeForOutput(name.string()).string());
- } else if (depth == 2 && tag == "uses-permission") {
- String8 error;
- String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
- if (error != "") {
- fprintf(stderr, "ERROR: %s\n", error.string());
- goto bail;
- }
- printUsesPermission(name,
- AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
- AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
}
}
} else if (strcmp("badging", option) == 0) {
@@ -893,7 +1023,8 @@
Vector<FeatureGroup> featureGroups;
KeyedVector<String8, ImpliedFeature> impliedFeatures;
- while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT &&
+ code != ResXMLTree::BAD_DOCUMENT) {
if (code == ResXMLTree::END_TAG) {
depth--;
if (depth < 2) {
@@ -924,8 +1055,10 @@
ResTable::normalizeForOutput(aName.string()).string());
}
printf(" label='%s' icon='%s'\n",
- ResTable::normalizeForOutput(activityLabel.string()).string(),
- ResTable::normalizeForOutput(activityIcon.string()).string());
+ ResTable::normalizeForOutput(activityLabel.string())
+ .string(),
+ ResTable::normalizeForOutput(activityIcon.string())
+ .string());
}
if (isLeanbackLauncherActivity) {
printf("leanback-launchable-activity:");
@@ -934,9 +1067,12 @@
ResTable::normalizeForOutput(aName.string()).string());
}
printf(" label='%s' icon='%s' banner='%s'\n",
- ResTable::normalizeForOutput(activityLabel.string()).string(),
- ResTable::normalizeForOutput(activityIcon.string()).string(),
- ResTable::normalizeForOutput(activityBanner.string()).string());
+ ResTable::normalizeForOutput(activityLabel.string())
+ .string(),
+ ResTable::normalizeForOutput(activityIcon.string())
+ .string(),
+ ResTable::normalizeForOutput(activityBanner.string())
+ .string());
}
}
if (!hasIntentFilter) {
@@ -964,18 +1100,21 @@
hasLauncher |= catLauncher;
hasCameraActivity |= actCamera;
hasCameraSecureActivity |= actCameraSecure;
- hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure;
+ hasOtherActivities |=
+ !actMainActivity && !actCamera && !actCameraSecure;
} else if (withinReceiver) {
hasWidgetReceivers |= actWidgetReceivers;
hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
hasBindDeviceAdminPermission);
- hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
+ hasOtherReceivers |=
+ (!actWidgetReceivers && !actDeviceAdminEnabled);
} else if (withinService) {
hasImeService |= actImeService;
hasWallpaperService |= actWallpaperService;
hasAccessibilityService |= (actAccessibilityService &&
hasBindAccessibilityServicePermission);
- hasPrintService |= (actPrintService && hasBindPrintServicePermission);
+ hasPrintService |=
+ (actPrintService && hasBindPrintServicePermission);
hasNotificationListenerService |= actNotificationListenerService &&
hasBindNotificationListenerServicePermission;
hasDreamService |= actDreamService && hasBindDreamServicePermission;
@@ -984,7 +1123,8 @@
!actHostApduService && !actOffHostApduService &&
!actNotificationListenerService);
} else if (withinProvider) {
- hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes;
+ hasDocumentsProvider |=
+ actDocumentsProvider && hasRequiredSafAttributes;
}
}
withinIntentFilter = false;
@@ -1125,7 +1265,8 @@
goto bail;
}
- String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, &error);
+ String8 banner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR,
+ &error);
if (error != "") {
fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n",
error.string());
@@ -1135,7 +1276,8 @@
ResTable::normalizeForOutput(label.string()).string());
printf("icon='%s'", ResTable::normalizeForOutput(icon.string()).string());
if (banner != "") {
- printf(" banner='%s'", ResTable::normalizeForOutput(banner.string()).string());
+ printf(" banner='%s'",
+ ResTable::normalizeForOutput(banner.string()).string());
}
printf("\n");
if (testOnly != 0) {
@@ -1178,13 +1320,15 @@
}
}
} else if (tag == "uses-sdk") {
- int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error);
+ int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR,
+ &error);
if (error != "") {
error = "";
String8 name = AaptXml::getResolvedAttribute(res, tree,
MIN_SDK_VERSION_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n",
+ fprintf(stderr,
+ "ERROR getting 'android:minSdkVersion' attribute: %s\n",
error.string());
goto bail;
}
@@ -1205,7 +1349,8 @@
String8 name = AaptXml::getResolvedAttribute(res, tree,
TARGET_SDK_VERSION_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
+ fprintf(stderr,
+ "ERROR getting 'android:targetSdkVersion' attribute: %s\n",
error.string());
goto bail;
}
@@ -1297,90 +1442,58 @@
}
} else if (tag == "uses-permission") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
- if (name != "" && error == "") {
- if (name == "android.permission.CAMERA") {
- addImpliedFeature(&impliedFeatures, "android.hardware.camera",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_FINE_LOCATION") {
- addImpliedFeature(&impliedFeatures, "android.hardware.location.gps",
- String8::format("requested %s permission", name.string())
- .string());
- addImpliedFeature(&impliedFeatures, "android.hardware.location",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_MOCK_LOCATION") {
- addImpliedFeature(&impliedFeatures, "android.hardware.location",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_COARSE_LOCATION") {
- addImpliedFeature(&impliedFeatures, "android.hardware.location.network",
- String8::format("requested %s permission", name.string())
- .string());
- addImpliedFeature(&impliedFeatures, "android.hardware.location",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" ||
- name == "android.permission.INSTALL_LOCATION_PROVIDER") {
- addImpliedFeature(&impliedFeatures, "android.hardware.location",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.BLUETOOTH" ||
- name == "android.permission.BLUETOOTH_ADMIN") {
- if (targetSdk > 4) {
- addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
- String8::format("requested %s permission", name.string())
- .string());
- addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth",
- "targetSdkVersion > 4");
- }
- } else if (name == "android.permission.RECORD_AUDIO") {
- addImpliedFeature(&impliedFeatures, "android.hardware.microphone",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.ACCESS_WIFI_STATE" ||
- name == "android.permission.CHANGE_WIFI_STATE" ||
- name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") {
- addImpliedFeature(&impliedFeatures, "android.hardware.wifi",
- String8::format("requested %s permission", name.string())
- .string());
- } else if (name == "android.permission.CALL_PHONE" ||
- name == "android.permission.CALL_PRIVILEGED" ||
- name == "android.permission.MODIFY_PHONE_STATE" ||
- name == "android.permission.PROCESS_OUTGOING_CALLS" ||
- name == "android.permission.READ_SMS" ||
- name == "android.permission.RECEIVE_SMS" ||
- name == "android.permission.RECEIVE_MMS" ||
- name == "android.permission.RECEIVE_WAP_PUSH" ||
- name == "android.permission.SEND_SMS" ||
- name == "android.permission.WRITE_APN_SETTINGS" ||
- name == "android.permission.WRITE_SMS") {
- addImpliedFeature(&impliedFeatures, "android.hardware.telephony",
- String8("requested a telephony permission").string());
- } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
- hasWriteExternalStoragePermission = true;
- } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
- hasReadExternalStoragePermission = true;
- } else if (name == "android.permission.READ_PHONE_STATE") {
- hasReadPhoneStatePermission = true;
- } else if (name == "android.permission.READ_CONTACTS") {
- hasReadContactsPermission = true;
- } else if (name == "android.permission.WRITE_CONTACTS") {
- hasWriteContactsPermission = true;
- } else if (name == "android.permission.READ_CALL_LOG") {
- hasReadCallLogPermission = true;
- } else if (name == "android.permission.WRITE_CALL_LOG") {
- hasWriteCallLogPermission = true;
- }
-
- printUsesPermission(name,
- AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
- AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
- } else {
+ if (error != "") {
fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
error.string());
goto bail;
}
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n");
+ goto bail;
+ }
+
+ addImpliedFeaturesForPermission(targetSdk, name, &impliedFeatures, false);
+
+ if (name == "android.permission.WRITE_EXTERNAL_STORAGE") {
+ hasWriteExternalStoragePermission = true;
+ } else if (name == "android.permission.READ_EXTERNAL_STORAGE") {
+ hasReadExternalStoragePermission = true;
+ } else if (name == "android.permission.READ_PHONE_STATE") {
+ hasReadPhoneStatePermission = true;
+ } else if (name == "android.permission.READ_CONTACTS") {
+ hasReadContactsPermission = true;
+ } else if (name == "android.permission.WRITE_CONTACTS") {
+ hasWriteContactsPermission = true;
+ } else if (name == "android.permission.READ_CALL_LOG") {
+ hasReadCallLogPermission = true;
+ } else if (name == "android.permission.WRITE_CALL_LOG") {
+ hasWriteCallLogPermission = true;
+ }
+
+ printUsesPermission(name,
+ AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0,
+ AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
+
+ } else if (tag == "uses-permission-sdk-23" || tag == "uses-permission-sdk-m") {
+ String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
+
+ if (name == "") {
+ fprintf(stderr, "ERROR: missing 'android:name' for "
+ "uses-permission-sdk-23\n");
+ goto bail;
+ }
+
+ addImpliedFeaturesForPermission(targetSdk, name, &impliedFeatures, true);
+
+ printUsesPermissionSdk23(
+ name, AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR));
+
} else if (tag == "uses-package") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
@@ -1422,7 +1535,8 @@
} else if (tag == "package-verifier") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
- String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR, &error);
+ String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR,
+ &error);
if (publicKey != "" && error == "") {
printf("package-verifier: name='%s' publicKey='%s'\n",
ResTable::normalizeForOutput(name.string()).string(),
@@ -1485,12 +1599,18 @@
if (error == "") {
if (orien == 0 || orien == 6 || orien == 8) {
// Requests landscape, sensorLandscape, or reverseLandscape.
- addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape",
- "one or more activities have specified a landscape orientation");
+ addImpliedFeature(&impliedFeatures,
+ "android.hardware.screen.landscape",
+ "one or more activities have specified a "
+ "landscape orientation",
+ false);
} else if (orien == 1 || orien == 7 || orien == 9) {
// Requests portrait, sensorPortrait, or reversePortrait.
- addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait",
- "one or more activities have specified a portrait orientation");
+ addImpliedFeature(&impliedFeatures,
+ "android.hardware.screen.portrait",
+ "one or more activities have specified a "
+ "portrait orientation",
+ false);
}
}
} else if (tag == "uses-library") {
@@ -1524,8 +1644,10 @@
hasBindDeviceAdminPermission = true;
}
} else {
- fprintf(stderr, "ERROR getting 'android:permission' attribute for"
- " receiver '%s': %s\n", receiverName.string(), error.string());
+ fprintf(stderr,
+ "ERROR getting 'android:permission' attribute for"
+ " receiver '%s': %s\n",
+ receiverName.string(), error.string());
}
} else if (tag == "service") {
withinService = true;
@@ -1542,20 +1664,24 @@
if (error == "") {
if (permission == "android.permission.BIND_INPUT_METHOD") {
hasBindInputMethodPermission = true;
- } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
+ } else if (permission ==
+ "android.permission.BIND_ACCESSIBILITY_SERVICE") {
hasBindAccessibilityServicePermission = true;
- } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
+ } else if (permission ==
+ "android.permission.BIND_PRINT_SERVICE") {
hasBindPrintServicePermission = true;
- } else if (permission == "android.permission.BIND_NFC_SERVICE") {
+ } else if (permission ==
+ "android.permission.BIND_NFC_SERVICE") {
hasBindNfcServicePermission = true;
- } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
+ } else if (permission ==
+ "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") {
hasBindNotificationListenerServicePermission = true;
} else if (permission == "android.permission.BIND_DREAM_SERVICE") {
hasBindDreamServicePermission = true;
}
} else {
- fprintf(stderr, "ERROR getting 'android:permission' attribute for"
- " service '%s': %s\n", serviceName.string(), error.string());
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for "
+ "service '%s': %s\n", serviceName.string(), error.string());
}
} else if (tag == "provider") {
withinProvider = true;
@@ -1563,7 +1689,8 @@
bool exported = AaptXml::getResolvedIntegerAttribute(res, tree,
EXPORTED_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:"
+ fprintf(stderr,
+ "ERROR getting 'android:exported' attribute for provider:"
" %s\n", error.string());
goto bail;
}
@@ -1571,16 +1698,17 @@
bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute(
res, tree, GRANT_URI_PERMISSIONS_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:"
- " %s\n", error.string());
+ fprintf(stderr,
+ "ERROR getting 'android:grantUriPermissions' attribute for "
+ "provider: %s\n", error.string());
goto bail;
}
String8 permission = AaptXml::getResolvedAttribute(res, tree,
PERMISSION_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:"
- " %s\n", error.string());
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for "
+ "provider: %s\n", error.string());
goto bail;
}
@@ -1661,8 +1789,9 @@
} else if (withinService && tag == "meta-data") {
String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:name' attribute for"
- " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+ fprintf(stderr, "ERROR getting 'android:name' attribute for "
+ "meta-data tag in service '%s': %s\n", serviceName.string(),
+ error.string());
goto bail;
}
@@ -1676,8 +1805,9 @@
String8 xmlPath = AaptXml::getResolvedAttribute(res, tree,
RESOURCE_ATTR, &error);
if (error != "") {
- fprintf(stderr, "ERROR getting 'android:resource' attribute for"
- " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+ fprintf(stderr, "ERROR getting 'android:resource' attribute for "
+ "meta-data tag in service '%s': %s\n",
+ serviceName.string(), error.string());
goto bail;
}
@@ -1731,15 +1861,19 @@
actImeService = true;
} else if (action == "android.service.wallpaper.WallpaperService") {
actWallpaperService = true;
- } else if (action == "android.accessibilityservice.AccessibilityService") {
+ } else if (action ==
+ "android.accessibilityservice.AccessibilityService") {
actAccessibilityService = true;
- } else if (action == "android.printservice.PrintService") {
+ } else if (action =="android.printservice.PrintService") {
actPrintService = true;
- } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
+ } else if (action ==
+ "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
actHostApduService = true;
- } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
+ } else if (action ==
+ "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
actOffHostApduService = true;
- } else if (action == "android.service.notification.NotificationListenerService") {
+ } else if (action ==
+ "android.service.notification.NotificationListenerService") {
actNotificationListenerService = true;
} else if (action == "android.service.dreams.DreamService") {
actDreamService = true;
@@ -1814,12 +1948,12 @@
}
addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen",
- "default feature for all apps");
+ "default feature for all apps", false);
const size_t numFeatureGroups = featureGroups.size();
if (numFeatureGroups == 0) {
// If no <feature-group> tags were defined, apply auto-implied features.
- printFeatureGroup(commonFeatures, &impliedFeatures);
+ printDefaultFeatureGroup(commonFeatures, impliedFeatures);
} else {
// <feature-group> tags are defined, so we ignore implied features and
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index b09a14f..63411b0 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1634,6 +1634,11 @@
}
@Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) {
+ // pass
+ }
+
+ @Override
public void sendStickyOrderedBroadcastAsUser(Intent intent,
UserHandle user, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,