Merge "Fixes recents from expanding full size when an app is docked"
diff --git a/Android.mk b/Android.mk
index 5d902c2..405f957 100644
--- a/Android.mk
+++ b/Android.mk
@@ -45,6 +45,7 @@
core/java/android/app/admin/SecurityLogTags.logtags \
core/java/android/content/EventLogTags.logtags \
core/java/android/speech/tts/EventLogTags.logtags \
+ core/java/android/net/EventLogTags.logtags \
core/java/android/webkit/EventLogTags.logtags \
core/java/com/android/internal/logging/EventLogTags.logtags \
diff --git "a/\135" "b/\135"
new file mode 100644
index 0000000..5619151
--- /dev/null
+++ "b/\135"
@@ -0,0 +1,12 @@
+NetworkNotificationManager: logging improvements
+
+TODO: squash me
+# Please enter the commit message for your changes. Lines starting
+# with '#' will be ignored, and an empty message aborts the commit.
+# On branch notification_tagging
+# Your branch is ahead of 'goog/master' by 2 commits.
+# (use "git push" to publish your local commits)
+#
+# Changes to be committed:
+# modified: services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+#
diff --git a/api/current.txt b/api/current.txt
index bab071b..5020a425 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2804,9 +2804,14 @@
public static class GestureDescription.StrokeDescription {
ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
+ ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long, int, boolean);
+ method public int getContinuedStrokeId();
method public long getDuration();
+ method public int getId();
method public android.graphics.Path getPath();
method public long getStartTime();
+ method public boolean isContinued();
+ field public static final int INVALID_STROKE_ID = -1; // 0xffffffff
}
}
@@ -6185,6 +6190,7 @@
field public static final deprecated java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME";
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM";
field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+ field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
@@ -8816,6 +8822,7 @@
field public static final java.lang.String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE";
field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB";
field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST";
+ field public static final java.lang.String CATEGORY_TYPED_OPENABLE = "android.intent.category.TYPED_OPENABLE";
field public static final java.lang.String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
field public static final java.lang.String CATEGORY_VOICE = "android.intent.category.VOICE";
field public static final android.os.Parcelable.Creator<android.content.Intent> CREATOR;
@@ -29681,6 +29688,7 @@
field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
+ field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
@@ -37260,6 +37268,7 @@
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+ field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
field public static final java.lang.String KEY_MMS_ALIAS_MIN_CHARS_INT = "aliasMinChars";
@@ -44384,7 +44393,7 @@
field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
field public static final deprecated int FLAG_BLUR_BEHIND = 4; // 0x4
field public static final int FLAG_DIM_BEHIND = 2; // 0x2
- field public static final int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
+ field public static final deprecated int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
field public static final deprecated int FLAG_DITHER = 4096; // 0x1000
field public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = -2147483648; // 0x80000000
field public static final int FLAG_FORCE_NOT_FULLSCREEN = 2048; // 0x800
diff --git a/api/system-current.txt b/api/system-current.txt
index 4a2f2d5..f23c1fc 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2918,9 +2918,14 @@
public static class GestureDescription.StrokeDescription {
ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
+ ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long, int, boolean);
+ method public int getContinuedStrokeId();
method public long getDuration();
+ method public int getId();
method public android.graphics.Path getPath();
method public long getStartTime();
+ method public boolean isContinued();
+ field public static final int INVALID_STROKE_ID = -1; // 0xffffffff
}
}
@@ -6374,6 +6379,7 @@
field public static final deprecated java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME";
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM";
field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+ field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
@@ -9176,6 +9182,7 @@
field public static final java.lang.String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE";
field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB";
field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST";
+ field public static final java.lang.String CATEGORY_TYPED_OPENABLE = "android.intent.category.TYPED_OPENABLE";
field public static final java.lang.String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
field public static final java.lang.String CATEGORY_VOICE = "android.intent.category.VOICE";
field public static final android.os.Parcelable.Creator<android.content.Intent> CREATOR;
@@ -32296,6 +32303,7 @@
field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
+ field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
@@ -35638,6 +35646,7 @@
field public static final int WIFI_SLEEP_POLICY_DEFAULT = 0; // 0x0
field public static final int WIFI_SLEEP_POLICY_NEVER = 2; // 0x2
field public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED = 1; // 0x1
+ field public static final java.lang.String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
field public static final java.lang.String WIFI_WATCHDOG_ON = "wifi_watchdog_on";
field public static final java.lang.String WINDOW_ANIMATION_SCALE = "window_animation_scale";
}
@@ -40330,6 +40339,7 @@
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+ field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
field public static final java.lang.String KEY_MMS_ALIAS_MIN_CHARS_INT = "aliasMinChars";
@@ -47539,7 +47549,7 @@
field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
field public static final deprecated int FLAG_BLUR_BEHIND = 4; // 0x4
field public static final int FLAG_DIM_BEHIND = 2; // 0x2
- field public static final int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
+ field public static final deprecated int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
field public static final deprecated int FLAG_DITHER = 4096; // 0x1000
field public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = -2147483648; // 0x80000000
field public static final int FLAG_FORCE_NOT_FULLSCREEN = 2048; // 0x800
diff --git a/api/test-current.txt b/api/test-current.txt
index b888911..810680d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2804,9 +2804,14 @@
public static class GestureDescription.StrokeDescription {
ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
+ ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long, int, boolean);
+ method public int getContinuedStrokeId();
method public long getDuration();
+ method public int getId();
method public android.graphics.Path getPath();
method public long getStartTime();
+ method public boolean isContinued();
+ field public static final int INVALID_STROKE_ID = -1; // 0xffffffff
}
}
@@ -6207,6 +6212,7 @@
field public static final deprecated java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME";
field public static final java.lang.String EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM = "android.app.extra.PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM";
field public static final java.lang.String EXTRA_PROVISIONING_EMAIL_ADDRESS = "android.app.extra.PROVISIONING_EMAIL_ADDRESS";
+ field public static final java.lang.String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
@@ -8841,6 +8847,7 @@
field public static final java.lang.String CATEGORY_SELECTED_ALTERNATIVE = "android.intent.category.SELECTED_ALTERNATIVE";
field public static final java.lang.String CATEGORY_TAB = "android.intent.category.TAB";
field public static final java.lang.String CATEGORY_TEST = "android.intent.category.TEST";
+ field public static final java.lang.String CATEGORY_TYPED_OPENABLE = "android.intent.category.TYPED_OPENABLE";
field public static final java.lang.String CATEGORY_UNIT_TEST = "android.intent.category.UNIT_TEST";
field public static final java.lang.String CATEGORY_VOICE = "android.intent.category.VOICE";
field public static final android.os.Parcelable.Creator<android.content.Intent> CREATOR;
@@ -29770,6 +29777,7 @@
field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
+ field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
@@ -37356,6 +37364,7 @@
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+ field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
field public static final java.lang.String KEY_MMS_ALIAS_MIN_CHARS_INT = "aliasMinChars";
@@ -44640,7 +44649,7 @@
field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
field public static final deprecated int FLAG_BLUR_BEHIND = 4; // 0x4
field public static final int FLAG_DIM_BEHIND = 2; // 0x2
- field public static final int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
+ field public static final deprecated int FLAG_DISMISS_KEYGUARD = 4194304; // 0x400000
field public static final deprecated int FLAG_DITHER = 4096; // 0x1000
field public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = -2147483648; // 0x80000000
field public static final int FLAG_FORCE_NOT_FULLSCREEN = 2048; // 0x800
diff --git a/core/java/android/accessibilityservice/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java
index d9b03fa..c9da152 100644
--- a/core/java/android/accessibilityservice/GestureDescription.java
+++ b/core/java/android/accessibilityservice/GestureDescription.java
@@ -23,10 +23,6 @@
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.MotionEvent.PointerCoords;
-import android.view.MotionEvent.PointerProperties;
import java.util.ArrayList;
import java.util.List;
@@ -128,9 +124,14 @@
for (int i = 0; i < mStrokes.size(); i++) {
StrokeDescription strokeDescription = mStrokes.get(i);
if (strokeDescription.hasPointForTime(time)) {
- touchPoints[numPointsFound].mPathIndex = i;
- touchPoints[numPointsFound].mIsStartOfPath = (time == strokeDescription.mStartTime);
- touchPoints[numPointsFound].mIsEndOfPath = (time == strokeDescription.mEndTime);
+ touchPoints[numPointsFound].mStrokeId = strokeDescription.getId();
+ touchPoints[numPointsFound].mContinuedStrokeId =
+ strokeDescription.getContinuedStrokeId();
+ touchPoints[numPointsFound].mIsStartOfPath =
+ (strokeDescription.getContinuedStrokeId() < 0)
+ && (time == strokeDescription.mStartTime);
+ touchPoints[numPointsFound].mIsEndOfPath = !strokeDescription.isContinued()
+ && (time == strokeDescription.mEndTime);
strokeDescription.getPosForTime(time, mTempPos);
touchPoints[numPointsFound].mX = Math.round(mTempPos[0]);
touchPoints[numPointsFound].mY = Math.round(mTempPos[1]);
@@ -196,6 +197,10 @@
* Immutable description of stroke that can be part of a gesture.
*/
public static class StrokeDescription {
+ public static final int INVALID_STROKE_ID = -1;
+
+ static int sIdCounter;
+
Path mPath;
long mStartTime;
long mEndTime;
@@ -203,6 +208,9 @@
private PathMeasure mPathMeasure;
// The tap location is only set for zero-length paths
float[] mTapLocation;
+ int mId;
+ boolean mContinued;
+ int mContinuedStrokeId;
/**
* @param path The path to follow. Must have exactly one contour. The bounds of the path
@@ -216,6 +224,32 @@
public StrokeDescription(@NonNull Path path,
@IntRange(from = 0) long startTime,
@IntRange(from = 0) long duration) {
+ this(path, startTime, duration, INVALID_STROKE_ID, false);
+ }
+
+ /**
+ * @param path The path to follow. Must have exactly one contour. The bounds of the path
+ * must not be negative. The path must not be empty. If the path has zero length
+ * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
+ * @param startTime The time, in milliseconds, from the time the gesture starts to the
+ * time the stroke should start. Must not be negative.
+ * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
+ * Must be positive.
+ * @param continuedStrokeId The ID of the stroke that this stroke continues, or
+ * {@link #INVALID_STROKE_ID} if it continues no stroke. The stroke it
+ * continues must have its isContinued flag set to {@code true} and must be in the
+ * gesture dispatched immediately before the one containing this stroke.
+ * @param isContinued {@code true} if this stroke will be continued by one in the
+ * next gesture {@code false} otherwise. Continued strokes keep their pointers down when
+ * the gesture completes.
+ */
+ public StrokeDescription(@NonNull Path path,
+ @IntRange(from = 0) long startTime,
+ @IntRange(from = 0) long duration,
+ @IntRange(from = 0) int continuedStrokeId,
+ boolean isContinued) {
+ mContinued = isContinued;
+ mContinuedStrokeId = continuedStrokeId;
if (duration <= 0) {
throw new IllegalArgumentException("Duration must be positive");
}
@@ -252,6 +286,7 @@
mStartTime = startTime;
mEndTime = startTime + duration;
mTimeToLengthConversion = getLength() / duration;
+ mId = sIdCounter++;
}
/**
@@ -281,6 +316,34 @@
return mEndTime - mStartTime;
}
+ /**
+ * Get the stroke's ID. The ID is used when a stroke is to be continued by another
+ * stroke in a future gesture.
+ *
+ * @return the ID of this stroke
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Check if this stroke is marked to continue in the next gesture.
+ *
+ * @return {@code true} if the stroke is to be continued.
+ */
+ public boolean isContinued() {
+ return mContinued;
+ }
+
+ /**
+ * Get the ID of the stroke that this one will continue.
+ *
+ * @return The ID of the stroke that this stroke continues, or 0 if no such stroke exists.
+ */
+ public int getContinuedStrokeId() {
+ return mContinuedStrokeId;
+ }
+
float getLength() {
return mPathMeasure.getLength();
}
@@ -314,11 +377,12 @@
private static final int FLAG_IS_START_OF_PATH = 0x01;
private static final int FLAG_IS_END_OF_PATH = 0x02;
- int mPathIndex;
- boolean mIsStartOfPath;
- boolean mIsEndOfPath;
- float mX;
- float mY;
+ public int mStrokeId;
+ public int mContinuedStrokeId;
+ public boolean mIsStartOfPath;
+ public boolean mIsEndOfPath;
+ public float mX;
+ public float mY;
public TouchPoint() {
}
@@ -328,7 +392,8 @@
}
public TouchPoint(Parcel parcel) {
- mPathIndex = parcel.readInt();
+ mStrokeId = parcel.readInt();
+ mContinuedStrokeId = parcel.readInt();
int startEnd = parcel.readInt();
mIsStartOfPath = (startEnd & FLAG_IS_START_OF_PATH) != 0;
mIsEndOfPath = (startEnd & FLAG_IS_END_OF_PATH) != 0;
@@ -336,8 +401,9 @@
mY = parcel.readFloat();
}
- void copyFrom(TouchPoint other) {
- mPathIndex = other.mPathIndex;
+ public void copyFrom(TouchPoint other) {
+ mStrokeId = other.mStrokeId;
+ mContinuedStrokeId = other.mContinuedStrokeId;
mIsStartOfPath = other.mIsStartOfPath;
mIsEndOfPath = other.mIsEndOfPath;
mX = other.mX;
@@ -351,7 +417,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mPathIndex);
+ dest.writeInt(mStrokeId);
+ dest.writeInt(mContinuedStrokeId);
int startEnd = mIsStartOfPath ? FLAG_IS_START_OF_PATH : 0;
startEnd |= mIsEndOfPath ? FLAG_IS_END_OF_PATH : 0;
dest.writeInt(startEnd);
@@ -426,30 +493,15 @@
}
/**
- * Class to convert a GestureDescription to a series of MotionEvents.
+ * Class to convert a GestureDescription to a series of GestureSteps.
*
* @hide
*/
public static class MotionEventGenerator {
- /**
- * Constants used to initialize all MotionEvents
- */
- private static final int EVENT_META_STATE = 0;
- private static final int EVENT_BUTTON_STATE = 0;
- private static final int EVENT_DEVICE_ID = 0;
- private static final int EVENT_EDGE_FLAGS = 0;
- private static final int EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN;
- private static final int EVENT_FLAGS = 0;
- private static final float EVENT_X_PRECISION = 1;
- private static final float EVENT_Y_PRECISION = 1;
-
/* Lazily-created scratch memory for processing touches */
private static TouchPoint[] sCurrentTouchPoints;
- private static TouchPoint[] sLastTouchPoints;
- private static PointerCoords[] sPointerCoords;
- private static PointerProperties[] sPointerProps;
- static List<GestureStep> getGestureStepsFromGestureDescription(
+ public static List<GestureStep> getGestureStepsFromGestureDescription(
GestureDescription description, int sampleTimeMs) {
final List<GestureStep> gestureSteps = new ArrayList<>();
@@ -474,31 +526,6 @@
return gestureSteps;
}
- public static List<MotionEvent> getMotionEventsFromGestureSteps(List<GestureStep> steps) {
- final List<MotionEvent> motionEvents = new ArrayList<>();
-
- // Number of points in last touch event
- int lastTouchPointSize = 0;
- TouchPoint[] lastTouchPoints;
-
- for (int i = 0; i < steps.size(); i++) {
- GestureStep step = steps.get(i);
- int currentTouchPointSize = step.numTouchPoints;
- lastTouchPoints = getLastTouchPoints(
- Math.max(lastTouchPointSize, currentTouchPointSize));
-
- appendMoveEventIfNeeded(motionEvents, lastTouchPoints, lastTouchPointSize,
- step.touchPoints, currentTouchPointSize, step.timeSinceGestureStart);
- lastTouchPointSize = appendUpEvents(motionEvents, lastTouchPoints,
- lastTouchPointSize, step.touchPoints, currentTouchPointSize,
- step.timeSinceGestureStart);
- lastTouchPointSize = appendDownEvents(motionEvents, lastTouchPoints,
- lastTouchPointSize, step.touchPoints, currentTouchPointSize,
- step.timeSinceGestureStart);
- }
- return motionEvents;
- }
-
private static TouchPoint[] getCurrentTouchPoints(int requiredCapacity) {
if ((sCurrentTouchPoints == null) || (sCurrentTouchPoints.length < requiredCapacity)) {
sCurrentTouchPoints = new TouchPoint[requiredCapacity];
@@ -508,133 +535,5 @@
}
return sCurrentTouchPoints;
}
-
- private static TouchPoint[] getLastTouchPoints(int requiredCapacity) {
- if ((sLastTouchPoints == null) || (sLastTouchPoints.length < requiredCapacity)) {
- sLastTouchPoints = new TouchPoint[requiredCapacity];
- for (int i = 0; i < requiredCapacity; i++) {
- sLastTouchPoints[i] = new TouchPoint();
- }
- }
- return sLastTouchPoints;
- }
-
- private static PointerCoords[] getPointerCoords(int requiredCapacity) {
- if ((sPointerCoords == null) || (sPointerCoords.length < requiredCapacity)) {
- sPointerCoords = new PointerCoords[requiredCapacity];
- for (int i = 0; i < requiredCapacity; i++) {
- sPointerCoords[i] = new PointerCoords();
- }
- }
- return sPointerCoords;
- }
-
- private static PointerProperties[] getPointerProps(int requiredCapacity) {
- if ((sPointerProps == null) || (sPointerProps.length < requiredCapacity)) {
- sPointerProps = new PointerProperties[requiredCapacity];
- for (int i = 0; i < requiredCapacity; i++) {
- sPointerProps[i] = new PointerProperties();
- }
- }
- return sPointerProps;
- }
-
- private static void appendMoveEventIfNeeded(List<MotionEvent> motionEvents,
- TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
- TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
- /* Look for pointers that have moved */
- boolean moveFound = false;
- for (int i = 0; i < currentTouchPointsSize; i++) {
- int lastPointsIndex = findPointByPathIndex(lastTouchPoints, lastTouchPointsSize,
- currentTouchPoints[i].mPathIndex);
- if (lastPointsIndex >= 0) {
- moveFound |= (lastTouchPoints[lastPointsIndex].mX != currentTouchPoints[i].mX)
- || (lastTouchPoints[lastPointsIndex].mY != currentTouchPoints[i].mY);
- lastTouchPoints[lastPointsIndex].copyFrom(currentTouchPoints[i]);
- }
- }
-
- if (moveFound) {
- long downTime = motionEvents.get(motionEvents.size() - 1).getDownTime();
- motionEvents.add(obtainMotionEvent(downTime, currentTime, MotionEvent.ACTION_MOVE,
- lastTouchPoints, lastTouchPointsSize));
- }
- }
-
- private static int appendUpEvents(List<MotionEvent> motionEvents,
- TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
- TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
- /* Look for a pointer at the end of its path */
- for (int i = 0; i < currentTouchPointsSize; i++) {
- if (currentTouchPoints[i].mIsEndOfPath) {
- int indexOfUpEvent = findPointByPathIndex(lastTouchPoints, lastTouchPointsSize,
- currentTouchPoints[i].mPathIndex);
- if (indexOfUpEvent < 0) {
- continue; // Should not happen
- }
- long downTime = motionEvents.get(motionEvents.size() - 1).getDownTime();
- int action = (lastTouchPointsSize == 1) ? MotionEvent.ACTION_UP
- : MotionEvent.ACTION_POINTER_UP;
- action |= indexOfUpEvent << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- motionEvents.add(obtainMotionEvent(downTime, currentTime, action,
- lastTouchPoints, lastTouchPointsSize));
- /* Remove this point from lastTouchPoints */
- for (int j = indexOfUpEvent; j < lastTouchPointsSize - 1; j++) {
- lastTouchPoints[j].copyFrom(lastTouchPoints[j+1]);
- }
- lastTouchPointsSize--;
- }
- }
- return lastTouchPointsSize;
- }
-
- private static int appendDownEvents(List<MotionEvent> motionEvents,
- TouchPoint[] lastTouchPoints, int lastTouchPointsSize,
- TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
- /* Look for a pointer that is just starting */
- for (int i = 0; i < currentTouchPointsSize; i++) {
- if (currentTouchPoints[i].mIsStartOfPath) {
- /* Add the point to last coords and use the new array to generate the event */
- lastTouchPoints[lastTouchPointsSize++].copyFrom(currentTouchPoints[i]);
- int action = (lastTouchPointsSize == 1) ? MotionEvent.ACTION_DOWN
- : MotionEvent.ACTION_POINTER_DOWN;
- long downTime = (action == MotionEvent.ACTION_DOWN) ? currentTime :
- motionEvents.get(motionEvents.size() - 1).getDownTime();
- action |= i << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- motionEvents.add(obtainMotionEvent(downTime, currentTime, action,
- lastTouchPoints, lastTouchPointsSize));
- }
- }
- return lastTouchPointsSize;
- }
-
- private static MotionEvent obtainMotionEvent(long downTime, long eventTime, int action,
- TouchPoint[] touchPoints, int touchPointsSize) {
- PointerCoords[] pointerCoords = getPointerCoords(touchPointsSize);
- PointerProperties[] pointerProperties = getPointerProps(touchPointsSize);
- for (int i = 0; i < touchPointsSize; i++) {
- pointerProperties[i].id = touchPoints[i].mPathIndex;
- pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
- pointerCoords[i].clear();
- pointerCoords[i].pressure = 1.0f;
- pointerCoords[i].size = 1.0f;
- pointerCoords[i].x = touchPoints[i].mX;
- pointerCoords[i].y = touchPoints[i].mY;
- }
- return MotionEvent.obtain(downTime, eventTime, action, touchPointsSize,
- pointerProperties, pointerCoords, EVENT_META_STATE, EVENT_BUTTON_STATE,
- EVENT_X_PRECISION, EVENT_Y_PRECISION, EVENT_DEVICE_ID, EVENT_EDGE_FLAGS,
- EVENT_SOURCE, EVENT_FLAGS);
- }
-
- private static int findPointByPathIndex(TouchPoint[] touchPoints, int touchPointsSize,
- int pathIndex) {
- for (int i = 0; i < touchPointsSize; i++) {
- if (touchPoints[i].mPathIndex == pathIndex) {
- return i;
- }
- }
- return -1;
- }
}
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 6e2c464..f29e576 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -200,6 +200,7 @@
@Nullable Message cancelCallback) {
this(context);
mCancelable = cancelable;
+ updateWindowForCancelable();
mCancelMessage = cancelCallback;
}
@@ -207,6 +208,7 @@
@Nullable OnCancelListener cancelListener) {
this(context);
mCancelable = cancelable;
+ updateWindowForCancelable();
setOnCancelListener(cancelListener);
}
@@ -1187,6 +1189,7 @@
*/
public void setCancelable(boolean flag) {
mCancelable = flag;
+ updateWindowForCancelable();
}
/**
@@ -1200,6 +1203,7 @@
public void setCanceledOnTouchOutside(boolean cancel) {
if (cancel && !mCancelable) {
mCancelable = true;
+ updateWindowForCancelable();
}
mWindow.setCloseOnTouchOutside(cancel);
@@ -1351,4 +1355,8 @@
}
}
}
+
+ private void updateWindowForCancelable() {
+ mWindow.setCloseOnSwipeEnabled(mCancelable);
+ }
}
diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
index c1646bb..b6b14f2 100644
--- a/core/java/android/app/admin/ConnectEvent.java
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -31,6 +31,7 @@
/** The destination port number. */
private final int port;
+ /** @hide */
public ConnectEvent(String ipAddress, int port, String packageName, long timestamp) {
super(packageName, timestamp);
this.ipAddress = ipAddress;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 39c8b79..5600da6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -151,6 +151,7 @@
* <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_SKIP_USER_CONSENT}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION}, optional</li>
* </ul>
*
* <p>When managed provisioning has completed, broadcasts are sent to the application specified
@@ -513,6 +514,19 @@
= "android.app.extra.PROVISIONING_ACCOUNT_TO_MIGRATE";
/**
+ * Boolean extra that is used in conjunction with
+ * {@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE}. If it's set to {@code true}, the account will
+ * not be removed from the primary user after it is migrated to the newly created user or
+ * profile.
+ *
+ * <p> Defaults to {@code false}
+ *
+ * <p> Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}.
+ */
+ public static final String EXTRA_PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION
+ = "android.app.extra.PROVISIONING_KEEP_ACCOUNT_ON_MIGRATION";
+
+ /**
* A String extra that, holds the email address of the account which a managed profile is
* created for. Used with {@link #ACTION_PROVISION_MANAGED_PROFILE} and
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}.
diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java
index 63ea8db..30e107c 100644
--- a/core/java/android/app/admin/DnsEvent.java
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -37,6 +37,7 @@
*/
private final int ipAddressesCount;
+ /** @hide */
public DnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
String packageName, long timestamp) {
super(packageName, timestamp);
diff --git a/core/java/android/app/admin/NetworkEvent.java b/core/java/android/app/admin/NetworkEvent.java
index 1dbff20..0de2665 100644
--- a/core/java/android/app/admin/NetworkEvent.java
+++ b/core/java/android/app/admin/NetworkEvent.java
@@ -16,6 +16,7 @@
package android.app.admin;
+import android.content.pm.PackageManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelFormatException;
@@ -35,21 +36,29 @@
/** The timestamp of the event being reported in milliseconds. */
long timestamp;
- protected NetworkEvent() {
+ /** @hide */
+ NetworkEvent() {
//empty constructor
}
- protected NetworkEvent(String packageName, long timestamp) {
+ /** @hide */
+ NetworkEvent(String packageName, long timestamp) {
this.packageName = packageName;
this.timestamp = timestamp;
}
- /** Returns the package name of the UID that performed the query. */
+ /**
+ * Returns the package name of the UID that performed the query, as returned by
+ * {@link PackageManager#getNameForUid}.
+ */
public String getPackageName() {
return packageName;
}
- /** Returns the timestamp of the event being reported in milliseconds. */
+ /**
+ * Returns the timestamp of the event being reported in milliseconds, the difference between
+ * the time the event was reported and midnight, January 1, 1970 UTC.
+ */
public long getTimestamp() {
return timestamp;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a5d7999..cda8176 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3483,6 +3483,21 @@
public static final String CATEGORY_OPENABLE = "android.intent.category.OPENABLE";
/**
+ * Used to indicate that an intent filter can accept files which are not necessarily
+ * openable by {@link ContentResolver#openFileDescriptor(Uri, String)}, but
+ * at least streamable via
+ * {@link ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)}
+ * using one of the stream types exposed via
+ * {@link ContentResolver#getStreamTypes(Uri, String)}.
+ *
+ * @see #ACTION_SEND
+ * @see #ACTION_SEND_MULTIPLE
+ */
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String CATEGORY_TYPED_OPENABLE =
+ "android.intent.category.TYPED_OPENABLE";
+
+ /**
* To be used as code under test for framework instrumentation tests.
*/
public static final String CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST =
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index b0d0d79..db24ffe 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -815,9 +815,9 @@
/*package*/ static final int STYLE_DENSITY = 5;
@FastNative
- /*package*/ native static final boolean applyStyle(long theme,
+ /*package*/ native static final void applyStyle(long theme,
int defStyleAttr, int defStyleRes, long xmlParser,
- int[] inAttrs, int[] outValues, int[] outIndices);
+ int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress);
@FastNative
/*package*/ native static final boolean resolveAttrs(long theme,
int defStyleAttr, int defStyleRes, int[] inValues,
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index c46fe29..eb010e4 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1126,7 +1126,7 @@
final XmlBlock.Parser parser = (XmlBlock.Parser) set;
AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
parser != null ? parser.mParseState : 0,
- attrs, array.mData, array.mIndices);
+ attrs, attrs.length, array.mDataAddress, array.mIndicesAddress);
array.mTheme = wrapper;
array.mXml = parser;
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 1e44a5c..ca95ce1 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -30,6 +30,8 @@
import com.android.internal.util.XmlUtils;
+import dalvik.system.VMRuntime;
+
import java.util.Arrays;
/**
@@ -44,28 +46,17 @@
public class TypedArray {
static TypedArray obtain(Resources res, int len) {
- final TypedArray attrs = res.mTypedArrayPool.acquire();
- if (attrs != null) {
- attrs.mLength = len;
- attrs.mRecycled = false;
-
- // Reset the assets, which may have changed due to configuration changes
- // or further resource loading.
- attrs.mAssets = res.getAssets();
-
- final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
- if (attrs.mData.length >= fullLen) {
- return attrs;
- }
-
- attrs.mData = new int[fullLen];
- attrs.mIndices = new int[1 + len];
- return attrs;
+ TypedArray attrs = res.mTypedArrayPool.acquire();
+ if (attrs == null) {
+ attrs = new TypedArray(res);
}
- return new TypedArray(res,
- new int[len*AssetManager.STYLE_NUM_ENTRIES],
- new int[1+len], len);
+ attrs.mRecycled = false;
+ // Reset the assets, which may have changed due to configuration changes
+ // or further resource loading.
+ attrs.mAssets = res.getAssets();
+ attrs.resize(len);
+ return attrs;
}
private final Resources mResources;
@@ -77,10 +68,25 @@
/*package*/ XmlBlock.Parser mXml;
/*package*/ Resources.Theme mTheme;
/*package*/ int[] mData;
+ /*package*/ long mDataAddress;
/*package*/ int[] mIndices;
+ /*package*/ long mIndicesAddress;
/*package*/ int mLength;
/*package*/ TypedValue mValue = new TypedValue();
+ private void resize(int len) {
+ mLength = len;
+ final int dataLen = len * AssetManager.STYLE_NUM_ENTRIES;
+ final int indicesLen = len + 1;
+ final VMRuntime runtime = VMRuntime.getRuntime();
+ if (mData == null || mData.length < dataLen) {
+ mData = (int[]) runtime.newNonMovableArray(int.class, dataLen);
+ mDataAddress = runtime.addressOf(mData);
+ mIndices = (int[]) runtime.newNonMovableArray(int.class, indicesLen);
+ mIndicesAddress = runtime.addressOf(mIndices);
+ }
+ }
+
/**
* Returns the number of values in this array.
*
@@ -1217,13 +1223,11 @@
return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]);
}
- /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) {
+ /** @hide */
+ protected TypedArray(Resources resources) {
mResources = resources;
mMetrics = mResources.getDisplayMetrics();
mAssets = mResources.getAssets();
- mData = data;
- mIndices = indices;
- mLength = len;
}
@Override
diff --git a/core/java/android/net/EventLogTags.logtags b/core/java/android/net/EventLogTags.logtags
new file mode 100644
index 0000000..d5ed014
--- /dev/null
+++ b/core/java/android/net/EventLogTags.logtags
@@ -0,0 +1,6 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package android.net
+
+50080 ntp_success (server|3),(rtt|2),(offset|2)
+50081 ntp_failure (server|3),(msg|3)
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index cf9243f..cea56b5 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -36,8 +36,7 @@
* }
* </pre>
*/
-public class SntpClient
-{
+public class SntpClient {
private static final String TAG = "SntpClient";
private static final boolean DBG = true;
@@ -88,6 +87,7 @@
try {
address = InetAddress.getByName(host);
} catch (Exception e) {
+ EventLogTags.writeNtpFailure(host, e.toString());
if (DBG) Log.d(TAG, "request time failed: " + e);
return false;
}
@@ -142,6 +142,7 @@
// = (transit + skew - transit + skew)/2
// = (2 * skew)/2 = skew
long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
+ EventLogTags.writeNtpSuccess(address.toString(), roundTripTime, clockOffset);
if (DBG) {
Log.d(TAG, "round trip: " + roundTripTime + "ms, " +
"clock offset: " + clockOffset + "ms");
@@ -153,6 +154,7 @@
mNtpTimeReference = responseTicks;
mRoundTripTime = roundTripTime;
} catch (Exception e) {
+ EventLogTags.writeNtpFailure(address.toString(), e.toString());
if (DBG) Log.d(TAG, "request time failed: " + e);
return false;
} finally {
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index 3b3fa69..0667495 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -42,6 +42,15 @@
public static final int NETWORK_DISCONNECTED = 7;
/** {@hide} */
+ public static final int NETWORK_FIRST_VALIDATION_SUCCESS = 8;
+ /** {@hide} */
+ public static final int NETWORK_REVALIDATION_SUCCESS = 9;
+ /** {@hide} */
+ public static final int NETWORK_FIRST_VALIDATION_PORTAL_FOUND = 10;
+ /** {@hide} */
+ public static final int NETWORK_REVALIDATION_PORTAL_FOUND = 11;
+
+ /** {@hide} */
@IntDef(value = {
NETWORK_CONNECTED,
NETWORK_VALIDATED,
@@ -50,6 +59,10 @@
NETWORK_LINGER,
NETWORK_UNLINGER,
NETWORK_DISCONNECTED,
+ NETWORK_FIRST_VALIDATION_SUCCESS,
+ NETWORK_REVALIDATION_SUCCESS,
+ NETWORK_FIRST_VALIDATION_PORTAL_FOUND,
+ NETWORK_REVALIDATION_PORTAL_FOUND,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index 1a31b56..a724ec1 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -44,10 +44,8 @@
public static final int DNS_FAILURE = 0;
public static final int DNS_SUCCESS = 1;
- /** {@hide} */
- @IntDef(value = {PROBE_DNS, PROBE_HTTP, PROBE_HTTPS, PROBE_PAC})
- @Retention(RetentionPolicy.SOURCE)
- public @interface ProbeType {}
+ private static final int FIRST_VALIDATION = 1 << 8;
+ private static final int REVALIDATION = 2 << 8;
/** {@hide} */
@IntDef(value = {DNS_FAILURE, DNS_SUCCESS})
@@ -56,12 +54,17 @@
public final int netId;
public final long durationMs;
- public final @ProbeType int probeType;
+ // probeType byte format (MSB to LSB):
+ // byte 0: unused
+ // byte 1: unused
+ // byte 2: 0 = UNKNOWN, 1 = FIRST_VALIDATION, 2 = REVALIDATION
+ // byte 3: PROBE_* constant
+ public final int probeType;
public final @ReturnCode int returnCode;
/** {@hide} */
public ValidationProbeEvent(
- int netId, long durationMs, @ProbeType int probeType, @ReturnCode int returnCode) {
+ int netId, long durationMs, int probeType, @ReturnCode int returnCode) {
this.netId = netId;
this.durationMs = durationMs;
this.probeType = probeType;
@@ -100,8 +103,18 @@
};
/** @hide */
+ public static int makeProbeType(int probeType, boolean firstValidation) {
+ return (probeType & 0xff) | (firstValidation ? FIRST_VALIDATION : REVALIDATION);
+ }
+
+ /** @hide */
public static String getProbeName(int probeType) {
- return Decoder.constants.get(probeType, "PROBE_???");
+ return Decoder.constants.get(probeType & 0xff, "PROBE_???");
+ }
+
+ /** @hide */
+ public static String getValidationStage(int probeType) {
+ return Decoder.constants.get(probeType & 0xff00, "UNKNOWN");
}
public static void logEvent(int netId, long durationMs, int probeType, int returnCode) {
@@ -109,12 +122,13 @@
@Override
public String toString() {
- return String.format("ValidationProbeEvent(%d, %s:%d, %dms)",
- netId, getProbeName(probeType), returnCode, durationMs);
+ return String.format("ValidationProbeEvent(%d, %s:%d %s, %dms)", netId,
+ getProbeName(probeType), returnCode, getValidationStage(probeType), durationMs);
}
final static class Decoder {
static final SparseArray<String> constants = MessageUtils.findMessageNames(
- new Class[]{ValidationProbeEvent.class}, new String[]{"PROBE_"});
+ new Class[]{ValidationProbeEvent.class},
+ new String[]{"PROBE_", "FIRST_", "REVALIDATION"});
}
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 9dafe29..d443b66 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -87,4 +87,6 @@
in String[] disallowedPackages);
boolean isUserUnlockingOrUnlocked(int userId);
int getManagedProfileBadge(int userId);
+ boolean isUserUnlocked(int userId);
+ boolean isUserRunning(int userId);
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 85f999b..e15f086 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1297,6 +1297,29 @@
}
/**
+ * Flatten a {@code List} containing arbitrary {@code Parcelable} objects into this parcel
+ * at the current position. They can later be retrieved using
+ * {@link #readParcelableList(List, ClassLoader)} if required.
+ *
+ * @see #readParcelableList(List, ClassLoader)
+ * @hide
+ */
+ public final <T extends Parcelable> void writeParcelableList(List<T> val, int flags) {
+ if (val == null) {
+ writeInt(-1);
+ return;
+ }
+
+ int N = val.size();
+ int i=0;
+ writeInt(N);
+ while (i < N) {
+ writeParcelable(val.get(i), flags);
+ i++;
+ }
+ }
+
+ /**
* Flatten a heterogeneous array containing a particular object type into
* the parcel, at
* the current dataPosition() and growing dataCapacity() if needed. The
@@ -2244,9 +2267,6 @@
* Read into the given List items IBinder objects that were written with
* {@link #writeBinderList} at the current dataPosition().
*
- * @return A newly created ArrayList containing strings with the same data
- * as those that were previously written.
- *
* @see #writeBinderList
*/
public final void readBinderList(List<IBinder> list) {
@@ -2265,6 +2285,34 @@
}
/**
+ * Read the list of {@code Parcelable} objects at the current data position into the
+ * given {@code list}. The contents of the {@code list} are replaced. If the serialized
+ * list was {@code null}, {@code list} is cleared.
+ *
+ * @see #writeParcelableList(List, int)
+ * @hide
+ */
+ public final <T extends Parcelable> void readParcelableList(List<T> list, ClassLoader cl) {
+ final int N = readInt();
+ if (N == -1) {
+ list.clear();
+ return;
+ }
+
+ final int M = list.size();
+ int i = 0;
+ for (; i < M && i < N; i++) {
+ list.set(i, (T) readParcelable(cl));
+ }
+ for (; i<N; i++) {
+ list.add((T) readParcelable(cl));
+ }
+ for (; i<M; i++) {
+ list.remove(N);
+ }
+ }
+
+ /**
* Read and return a new array containing a particular object type from
* the parcel at the current dataPosition(). Returns null if the
* previously written array was null. The array <em>must</em> have
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a79b0c4..0d3b328 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -187,6 +187,8 @@
* Specifies if a user is disallowed from configuring bluetooth.
* This does <em>not</em> restrict the user from turning bluetooth on or off.
* The default value is <code>false</code>.
+ * <p>This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
+ * bluetooth completely on the device, use {@link #DISALLOW_BLUETOOTH}.
* <p>This restriction has no effect in a managed profile.
*
* <p>Key for user restrictions.
@@ -198,6 +200,20 @@
public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
/**
+ * Specifies if bluetooth is disallowed on the device.
+ *
+ * <p> This restriction can only be set by the device owner and the profile owner on the
+ * primary user and it applies globally - i.e. it disables bluetooth on the entire device.
+ * <p>The default value is <code>false</code>.
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_BLUETOOTH = "no_bluetooth";
+
+ /**
* Specifies if a user is disallowed from transferring files over
* USB. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
@@ -996,10 +1012,9 @@
}
/** {@hide} */
- public boolean isUserRunning(int userId) {
- // TODO Switch to using UMS internal isUserRunning
+ public boolean isUserRunning(@UserIdInt int userId) {
try {
- return ActivityManager.getService().isUserRunning(userId, 0);
+ return mService.isUserRunning(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -1096,8 +1111,7 @@
/** {@hide} */
public boolean isUserUnlocked(@UserIdInt int userId) {
try {
- return ActivityManager.getService().isUserRunning(userId,
- ActivityManager.FLAG_AND_UNLOCKED);
+ return mService.isUserUnlocked(userId);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 4bdb92b..1447e7d 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -142,6 +142,12 @@
public abstract boolean isUserUnlockingOrUnlocked(int userId);
/**
+ * Return whether the given user is running in an
+ * {@code UserState.STATE_RUNNING_UNLOCKED} state.
+ */
+ public abstract boolean isUserUnlocked(int userId);
+
+ /**
* Return whether the given user is running
*/
public abstract boolean isUserRunning(int userId);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0946906..b5c7f7b 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7741,6 +7741,13 @@
public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
"wifi_scan_always_enabled";
+ /**
+ * Value to specify if Wi-Fi Wakeup feature is enabled.
+ * @hide
+ */
+ @SystemApi
+ public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
+
/**
* Settings to allow BLE scans to be enabled even when Bluetooth is turned off for
* connectivity.
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
index cb71ecc..debc170 100644
--- a/core/java/android/util/jar/StrictJarVerifier.java
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -17,7 +17,7 @@
package android.util.jar;
-import java.io.ByteArrayInputStream;
+import android.util.apk.ApkSignatureSchemeV2Verifier;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@@ -33,13 +33,9 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
-import android.util.ArraySet;
-import android.util.apk.ApkSignatureSchemeV2Verifier;
-import libcore.io.Base64;
import sun.security.jca.Providers;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
@@ -139,7 +135,7 @@
*/
void verify() {
byte[] d = digest.digest();
- if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
+ if (!verifyMessageDigest(d, hash)) {
throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
}
verifiedEntries.put(name, certChains);
@@ -490,12 +486,22 @@
md.update(data, start, end - start);
}
byte[] b = md.digest();
- byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
- return MessageDigest.isEqual(b, Base64.decode(hashBytes));
+ byte[] encodedHashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+ return verifyMessageDigest(b, encodedHashBytes);
}
return ignorable;
}
+ private static boolean verifyMessageDigest(byte[] expected, byte[] encodedActual) {
+ byte[] actual;
+ try {
+ actual = java.util.Base64.getDecoder().decode(encodedActual);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ return MessageDigest.isEqual(expected, actual);
+ }
+
/**
* Returns all of the {@link java.security.cert.Certificate} chains that
* were used to verify the signature on the JAR entry called
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 2c13831..e04c613 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -301,6 +301,7 @@
private boolean mDestroyed;
private boolean mOverlayWithDecorCaptionEnabled = false;
+ private boolean mCloseOnSwipeEnabled = false;
// The current window attributes.
private final WindowManager.LayoutParams mWindowAttributes =
@@ -2209,4 +2210,21 @@
* @hide
*/
public abstract void reportActivityRelaunched();
+
+ /**
+ * Called to set flag to check if the close on swipe is enabled. This will only function if
+ * FEATURE_SWIPE_TO_DISMISS has been set.
+ * @hide
+ */
+ public void setCloseOnSwipeEnabled(boolean closeOnSwipeEnabled) {
+ mCloseOnSwipeEnabled = closeOnSwipeEnabled;
+ }
+
+ /**
+ * @return {@code true} if the close on swipe is enabled.
+ * @hide
+ */
+ public boolean isCloseOnSwipeEnabled() {
+ return mCloseOnSwipeEnabled;
+ }
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index b8408dd..aa7631d 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -922,8 +922,11 @@
* unlock credential) than the user will still need to confirm it before
* seeing this window, unless {@link #FLAG_SHOW_WHEN_LOCKED} has
* also been set.
- * @see KeyguardManager#dismissKeyguard
+ * @deprecated Use {@link #FLAG_SHOW_WHEN_LOCKED} or {@link KeyguardManager#dismissKeyguard}
+ * instead. The Keyguard should never be dismissed automatically repeatedly as it also
+ * guards against unintentional touches.
*/
+ @Deprecated
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
/** Window flag: when set the window will accept for touch events
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index cdd267e..32876ac 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -572,7 +572,7 @@
String args[] = {
"--setuid=1000",
"--setgid=1000",
- "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007,3009,3010",
+ "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,3001,3002,3003,3006,3007,3009,3010",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 2a004cfb..8c682b9 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2502,6 +2502,7 @@
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
+ setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
@@ -2573,7 +2574,7 @@
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
- registerSwipeCallbacks();
+ registerSwipeCallbacks(contentParent);
}
// Remaining setup -- of background and title -- that only applies
@@ -2987,9 +2988,12 @@
return (mRightIconView = (ImageView)findViewById(R.id.right_icon));
}
- private void registerSwipeCallbacks() {
- SwipeDismissLayout swipeDismiss =
- (SwipeDismissLayout) findViewById(R.id.content);
+ private void registerSwipeCallbacks(ViewGroup contentParent) {
+ if (!(contentParent instanceof SwipeDismissLayout)) {
+ Log.w(TAG, "contentParent is not a SwipeDismissLayout: " + contentParent);
+ return;
+ }
+ SwipeDismissLayout swipeDismiss = (SwipeDismissLayout) contentParent;
swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() {
@Override
public void onDismissed(SwipeDismissLayout layout) {
@@ -3028,6 +3032,16 @@
});
}
+ /** @hide */
+ @Override
+ public void setCloseOnSwipeEnabled(boolean closeOnSwipeEnabled) {
+ if (hasFeature(Window.FEATURE_SWIPE_TO_DISMISS) // swipe-to-dismiss feature is requested
+ && mContentParent instanceof SwipeDismissLayout) { // check casting mContentParent
+ ((SwipeDismissLayout) mContentParent).setDismissable(closeOnSwipeEnabled);
+ }
+ super.setCloseOnSwipeEnabled(closeOnSwipeEnabled);
+ }
+
/**
* Helper method for calling the {@link Callback#onPanelClosed(int, Menu)}
* callback. This method will grab whatever extra state is needed for the
diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java
index d88f479..31e67bd 100644
--- a/core/java/com/android/internal/widget/SwipeDismissLayout.java
+++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java
@@ -110,6 +110,8 @@
private float mLastX;
+ private boolean mDismissable = true;
+
public SwipeDismissLayout(Context context) {
super(context);
init(context);
@@ -166,6 +168,10 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (!mDismissable) {
+ return super.onInterceptTouchEvent(ev);
+ }
+
// offset because the view is translated during swipe
ev.offsetLocation(mTranslationX, 0);
@@ -225,7 +231,7 @@
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mVelocityTracker == null) {
+ if (mVelocityTracker == null || !mDismissable) {
return super.onTouchEvent(ev);
}
// offset because the view is translated during swipe
@@ -363,4 +369,13 @@
return checkV && v.canScrollHorizontally((int) -dx);
}
+
+ public void setDismissable(boolean dismissable) {
+ if (!dismissable && mDismissable) {
+ cancel();
+ resetMembers();
+ }
+
+ mDismissable = dismissable;
+ }
}
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 10090a1..23a988a 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -25,6 +25,8 @@
#include <JNIHelp.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <android/hidl/base/1.0/IBase.h>
+#include <android/hidl/base/1.0/IHwBase.h>
#include <android_runtime/AndroidRuntime.h>
#include <hidl/ServiceManagement.h>
#include <hidl/Status.h>
@@ -239,11 +241,11 @@
using android::hidl::manager::V1_0::IServiceManager;
sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz);
-
+ sp<hidl::base::V1_0::IBase> base = hidl::base::V1_0::IHwBase::asInterface(binder);
bool ok = hardware::defaultServiceManager()->add(
interfaceChain,
serviceName,
- binder);
+ base);
env->ReleaseStringUTFChars(serviceNameObj, serviceName);
serviceName = NULL;
@@ -289,8 +291,10 @@
hardware::defaultServiceManager()->get(
ifaceName,
serviceName,
- [&service](sp<hardware::IBinder> out) {
- service = out;
+ [&service](sp<hidl::base::V1_0::IBase> out) {
+ service = hardware::toBinder<
+ hidl::base::V1_0::IBase, hidl::base::V1_0::IHwBase
+ >(out);
});
env->ReleaseStringUTFChars(ifaceNameObj, ifaceName);
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index d70fbb9..d382f24 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1179,67 +1179,17 @@
return result ? JNI_TRUE : JNI_FALSE;
}
-static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject clazz,
- jlong themeToken,
- jint defStyleAttr,
- jint defStyleRes,
- jlong xmlParserToken,
- jintArray attrs,
- jintArray outValues,
- jintArray outIndices)
-{
- if (themeToken == 0) {
- jniThrowNullPointerException(env, "theme token");
- return JNI_FALSE;
- }
- if (attrs == NULL) {
- jniThrowNullPointerException(env, "attrs");
- return JNI_FALSE;
- }
- if (outValues == NULL) {
- jniThrowNullPointerException(env, "out values");
- return JNI_FALSE;
- }
-
- const jsize NI = env->GetArrayLength(attrs);
- const jsize NV = env->GetArrayLength(outValues);
- if (NV < (NI*STYLE_NUM_ENTRIES)) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
- return JNI_FALSE;
- }
-
- jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
- if (src == NULL) {
- return JNI_FALSE;
- }
-
- jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- if (baseDest == NULL) {
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return JNI_FALSE;
- }
-
- jint* indices = NULL;
- if (outIndices != NULL) {
- if (env->GetArrayLength(outIndices) > NI) {
- indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
- }
- }
-
+static void android_content_AssetManager_applyStyle(JNIEnv* env, jobject, jlong themeToken,
+ jint defStyleAttr, jint defStyleRes, jlong xmlParserToken, jintArray attrsObj, jint length,
+ jlong outValuesAddress, jlong outIndicesAddress) {
+ jint* attrs = env->GetIntArrayElements(attrsObj, 0);
ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken);
- bool result = ApplyStyle(theme, xmlParser,
- defStyleAttr, defStyleRes,
- (uint32_t*) src, NI,
- (uint32_t*) baseDest,
- (uint32_t*) indices);
-
- if (indices != NULL) {
- env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
- }
- env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return result ? JNI_TRUE : JNI_FALSE;
+ uint32_t* outValues = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outValuesAddress));
+ uint32_t* outIndices = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outIndicesAddress));
+ ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes,
+ reinterpret_cast<const uint32_t*>(attrs), length, outValues, outIndices);
+ env->ReleaseIntArrayElements(attrsObj, attrs, JNI_ABORT);
}
static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz,
@@ -1795,7 +1745,7 @@
{ "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
(void*) android_content_AssetManager_dumpTheme },
// @FastNative
- { "applyStyle","(JIIJ[I[I[I)Z",
+ { "applyStyle","(JIIJ[IIJJ)V",
(void*) android_content_AssetManager_applyStyle },
// @FastNative
{ "resolveAttrs","(JII[I[I[I[I)Z",
diff --git a/core/res/res/layout/date_picker_material.xml b/core/res/res/layout/date_picker_material.xml
index 763f2a4..dd8a45d2 100644
--- a/core/res/res/layout/date_picker_material.xml
+++ b/core/res/res/layout/date_picker_material.xml
@@ -25,10 +25,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
- <include
- layout="@layout/date_picker_view_animator_material"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1" />
+ <ScrollView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fadeScrollbars="false"
+ android:scrollIndicators="top|bottom">
+ <include
+ layout="@layout/date_picker_view_animator_material"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+ </ScrollView>
</LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a7c5b2a..d8ae138 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -422,6 +422,9 @@
<!-- Boolean indicating whether Hotspot 2.0/Passpoint and ANQP queries is enabled -->
<bool translatable="false" name="config_wifi_hotspot2_enabled">false</bool>
+ <!-- Boolean indicating whether 802.11r Fast BSS Transition is enabled on this platform -->
+ <bool translatable="false" name="config_wifi_fast_bss_transition_enabled">false</bool>
+
<!-- Device type information conforming to Annex B format in WiFi Direct specification.
The default represents a dual-mode smartphone -->
<string translatable="false" name="config_wifi_p2p_device_type">10-0050F204-5</string>
@@ -1650,7 +1653,7 @@
<bool name="config_actionMenuItemAllCaps">true</bool>
<!-- Remote server that can provide NTP responses. -->
- <string translatable="false" name="config_ntpServer">2.android.pool.ntp.org</string>
+ <string translatable="false" name="config_ntpServer">time.android.com</string>
<!-- Normal polling frequency in milliseconds -->
<integer name="config_ntpPollingInterval">86400000</integer>
<!-- Try-again polling interval in milliseconds, in case the network request failed -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index af77b1f..423ec1f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4453,6 +4453,6 @@
<!-- Label used by Telephony code, assigned as the display name for conference calls [CHAR LIMIT=60] -->
<string name="conference_call">Conference Call</string>
- <!-- Title for a tooltip popup window [CHAR LIMIT=NONE] -->
- <string name="tooltip_popup_title">Tooltip Popup</string>
+ <!-- Window title for a tooltip [CHAR LIMIT=NONE] -->
+ <string name="tooltip_popup_title">Tooltip</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f6fd64b..a17d2f1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1716,6 +1716,7 @@
<java-symbol type="bool" name="config_wifi_background_scan_support" />
<java-symbol type="bool" name="config_wifi_dual_band_support" />
<java-symbol type="bool" name="config_wifi_hotspot2_enabled" />
+ <java-symbol type="bool" name="config_wifi_fast_bss_transition_enabled" />
<java-symbol type="bool" name="config_wimaxEnabled" />
<java-symbol type="bool" name="show_ongoing_ime_switcher" />
<java-symbol type="color" name="config_defaultNotificationColor" />
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 069f81b..9490436 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -155,7 +155,7 @@
@VisibleForTesting
public static int makeTag(char c1, char c2, char c3, char c4) {
- return (c1 << 24) + (c2 << 16) + (c3 << 8) + c4;
+ return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4;
}
private static boolean isSpacer(char c) {
@@ -228,8 +228,10 @@
return new Font(fullFilename, index, axes, weight, isItalic);
}
- /** The 'tag' attribute value is read as four character values between 0 and 255 inclusive. */
- private static final Pattern TAG_PATTERN = Pattern.compile("[\\x00-\\xFF]{4}");
+ /** The 'tag' attribute value is read as four character values between U+0020 and U+007E
+ * inclusive.
+ */
+ private static final Pattern TAG_PATTERN = Pattern.compile("[\\x20-\\x7E]{4}");
/** The 'styleValue' attribute has an optional leading '-', followed by '<digits>',
* '<digits>.<digits>', or '.<digits>' where '<digits>' is one or more of [0-9].
diff --git a/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java b/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
index e4d6aa8..d046c11 100644
--- a/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
+++ b/graphics/tests/graphicstests/src/android/graphics/VariationParserTest.java
@@ -105,9 +105,18 @@
}
@SmallTest
+ public void testInvalidTagCharacters() {
+ FontListParser.Axis[] axis =
+ FontListParser.parseFontVariationSettings("'\u0000\u0000\u0000\u0000' 10");
+ assertEquals(0, axis.length);
+ axis = FontListParser.parseFontVariationSettings("'\u3042\u3044\u3046\u3048' 10");
+ assertEquals(0, axis.length);
+ }
+
+ @SmallTest
public void testMakeTag() {
assertEquals(0x77647468, FontListParser.makeTag('w', 'd', 't', 'h'));
assertEquals(0x41582020, FontListParser.makeTag('A', 'X', ' ', ' '));
assertEquals(0x20202020, FontListParser.makeTag(' ', ' ', ' ', ' '));
}
-}
\ No newline at end of file
+}
diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp
index 00f7a42..d71fc39 100644
--- a/libs/androidfw/AttributeResolution.cpp
+++ b/libs/androidfw/AttributeResolution.cpp
@@ -193,9 +193,9 @@
return true;
}
-bool ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
- uint32_t def_style_res, uint32_t* attrs, size_t attrs_length, uint32_t* out_values,
- uint32_t* out_indices) {
+void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+ uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length,
+ uint32_t* out_values, uint32_t* out_indices) {
if (kDebugStyles) {
ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme,
def_style_attr, def_style_res, xml_parser);
@@ -376,7 +376,7 @@
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
- if (out_indices != NULL && value.dataType != Res_value::TYPE_NULL) {
+ if (value.dataType != Res_value::TYPE_NULL) {
indices_idx++;
out_indices[indices_idx] = ii;
}
@@ -386,10 +386,7 @@
res.unlock();
- if (out_indices != NULL) {
- out_indices[0] = indices_idx;
- }
- return true;
+ out_indices[0] = indices_idx;
}
bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs,
diff --git a/libs/androidfw/include/androidfw/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h
index 3ed8bce..8d5ff46 100644
--- a/libs/androidfw/include/androidfw/AttributeResolution.h
+++ b/libs/androidfw/include/androidfw/AttributeResolution.h
@@ -44,9 +44,9 @@
uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
-bool ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
- uint32_t def_style_res, uint32_t* attrs, size_t attrs_length, uint32_t* out_values,
- uint32_t* out_indices);
+void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+ uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length,
+ uint32_t* out_values, uint32_t* out_indices);
bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs,
size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
index d6d7890..3aac178 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -161,9 +161,9 @@
std::vector<uint32_t> values;
values.resize(arraysize(attrs) * 6);
- ASSERT_TRUE(ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/,
- 0 /*def_style_res*/, attrs, arraysize(attrs),
- values.data(), nullptr /*out_indices*/));
+ ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/,
+ 0 /*def_style_res*/, attrs, arraysize(attrs),
+ values.data(), nullptr /*out_indices*/);
const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 97b7dd7..f4ffa7a 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -574,7 +574,7 @@
}
bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
- return bitmap && width == bitmap->width() && height == bitmap->height();
+ return bitmap && width <= bitmap->width() && height <= bitmap->height();
}
void Tree::onPropertyChanged(TreeProperties* prop) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index e9a9c71..8244a39 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -630,10 +630,15 @@
}
void setScaledSize(int width, int height) {
- if (mNonAnimatableProperties.scaledWidth != width
- || mNonAnimatableProperties.scaledHeight != height) {
- mNonAnimatableProperties.scaledWidth = width;
- mNonAnimatableProperties.scaledHeight = height;
+ // If the requested size is bigger than what the bitmap was, then
+ // we increase the bitmap size to match. The width and height
+ // are bound by MAX_CACHED_BITMAP_SIZE.
+ if (mNonAnimatableProperties.scaledWidth < width
+ || mNonAnimatableProperties.scaledHeight < height) {
+ mNonAnimatableProperties.scaledWidth = std::max(width,
+ mNonAnimatableProperties.scaledWidth);
+ mNonAnimatableProperties.scaledHeight = std::max(height,
+ mNonAnimatableProperties.scaledHeight);
mNonAnimatablePropertiesDirty = true;
mTree->onPropertyChanged(this);
}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index bbb7184..52d5b7ca 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2765,23 +2765,28 @@
tag != null ? tag.name : null, dataFormat, numberOfComponents));
}
- if (tag == null || dataFormat <= 0 ||
- dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) {
- // Skip if the parsed tag number is not defined or invalid data format.
- if (DEBUG) {
- if (tag == null) {
- Log.w(TAG, "Skip tag entry since tag number is not defined: " + tagNumber);
- } else {
- Log.w(TAG, "Skip tag entry since data format is invalid: " + dataFormat);
- }
+ long byteCount = 0;
+ boolean valid = false;
+ if (tag == null) {
+ Log.w(TAG, "Skip the tag entry since tag number is not defined: " + tagNumber);
+ } else if (dataFormat <= 0 || dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) {
+ Log.w(TAG, "Skip the tag entry since data format is invalid: " + dataFormat);
+ } else {
+ byteCount = (long) numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
+ if (byteCount < 0 || byteCount > Integer.MAX_VALUE) {
+ Log.w(TAG, "Skip the tag entry since the number of components is invalid: "
+ + numberOfComponents);
+ } else {
+ valid = true;
}
+ }
+ if (!valid) {
dataInputStream.seek(nextEntryOffset);
continue;
}
// Read a value from data field or seek to the value offset which is stored in data
// field if the size of the entry value is bigger than 4.
- int byteCount = numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat];
if (byteCount > 4) {
int offset = dataInputStream.readInt();
if (DEBUG) {
@@ -2871,7 +2876,7 @@
continue;
}
- byte[] bytes = new byte[byteCount];
+ byte[] bytes = new byte[(int) byteCount];
dataInputStream.readFully(bytes);
ExifAttribute attribute = new ExifAttribute(dataFormat, numberOfComponents, bytes);
mAttributes[ifdType].put(tag.name, attribute);
diff --git a/packages/PrintRecommendationService/res/values/donottranslate.xml b/packages/PrintRecommendationService/res/values/donottranslate.xml
index e9b97a3..2cce56d8a 100644
--- a/packages/PrintRecommendationService/res/values/donottranslate.xml
+++ b/packages/PrintRecommendationService/res/values/donottranslate.xml
@@ -32,13 +32,6 @@
<item>Hewlett Packard</item>
</string-array>
- <!-- Samsung plugin -->
- <string-array name="known_print_vendor_info_for_samsung" translatable="false">
- <item>com.sec.app.samsungprintservice</item>
- <item>Samsung Electronics</item>
- <item>Samsung</item>
- </string-array>
-
<!-- Xerox plugin -->
<string-array name="known_print_vendor_info_for_xerox" translatable="false">
<item>com.xerox.printservice</item>
@@ -49,6 +42,8 @@
<array name="known_print_plugin_vendors" translatable="false">
<item>@array/known_print_vendor_info_for_mopria</item>
<item>@array/known_print_vendor_info_for_hp</item>
- <item>@array/known_print_vendor_info_for_samsung</item>
</array>
+
+ <!-- Samsung plugin -->
+ <string name="plugin_package_samsung">com.sec.app.samsungprintservice</string>
</resources>
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
index 3eedefd..d048396 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/RecommendationServiceImpl.java
@@ -78,7 +78,7 @@
try {
mPlugins.add(new RemotePrintServicePlugin(new SamsungRecommendationPlugin(this), this,
- false));
+ true));
} catch (Exception e) {
Log.e(LOG_TAG, "Could not initiate " + getString(R.string.plugin_vendor_samsung) +
" plugin", e);
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
index a2c0485..d60a25f 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
@@ -16,30 +16,52 @@
package com.android.printservice.recommendation.plugin.mdnsFilter;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.content.Context;
-import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
-import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+
import com.android.printservice.recommendation.PrintServicePlugin;
-import com.android.printservice.recommendation.util.DiscoveryListenerMultiplexer;
-import com.android.printservice.recommendation.util.NsdResolveQueue;
+import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
+import com.android.printservice.recommendation.util.MDNSUtils;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* A plugin listening for mDNS results and only adding the ones that {@link
* MDNSUtils#isVendorPrinter match} configured list
*/
-public class MDNSFilterPlugin implements PrintServicePlugin, NsdManager.DiscoveryListener {
- private static final String LOG_TAG = "MDNSFilterPlugin";
+public class MDNSFilterPlugin implements PrintServicePlugin {
- private static final String PRINTER_SERVICE_TYPE = "_ipp._tcp";
+ /** The mDNS service types supported */
+ private static final Set<String> PRINTER_SERVICE_TYPES = new HashSet<String>() {{
+ add("_ipp._tcp");
+ }};
+
+ /**
+ * The printer filter for {@link MDNSFilteredDiscovery} passing only mDNS results
+ * that {@link MDNSUtils#isVendorPrinter match} configured list
+ */
+ private static class VendorNameFilter implements MDNSFilteredDiscovery.PrinterFilter {
+ /** mDNS names handled by the print service this plugin is for */
+ private final @NonNull Set<String> mMDNSNames;
+
+ /**
+ * Filter constructor
+ *
+ * @param vendorNames The vendor names to pass
+ */
+ VendorNameFilter(@NonNull Set<String> vendorNames) {
+ mMDNSNames = new HashSet<>(vendorNames);
+ }
+
+ @Override
+ public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
+ return MDNSUtils.isVendorPrinter(nsdServiceInfo, mMDNSNames);
+ }
+ }
/** Name of the print service this plugin is for */
private final @StringRes int mName;
@@ -47,26 +69,8 @@
/** Package name of the print service this plugin is for */
private final @NonNull CharSequence mPackageName;
- /** mDNS names handled by the print service this plugin is for */
- private final @NonNull HashSet<String> mMDNSNames;
-
- /** Printer identifiers of the mPrinters found. */
- @GuardedBy("mLock")
- private final @NonNull HashSet<String> mPrinters;
-
- /** Context of the user of this plugin */
- private final @NonNull Context mContext;
-
- /**
- * Call back to report the number of mPrinters found.
- *
- * We assume that {@link #start} and {@link #stop} are never called in parallel, hence it is
- * safe to not synchronize access to this field.
- */
- private @Nullable PrinterDiscoveryCallback mCallback;
-
- /** Queue used to resolve nsd infos */
- private final @NonNull NsdResolveQueue mResolveQueue;
+ /** The mDNS filtered discovery */
+ private final MDNSFilteredDiscovery mMDNSFilteredDiscovery;
/**
* Create new stub that assumes that a print service can be used to print on all mPrinters
@@ -79,16 +83,11 @@
*/
public MDNSFilterPlugin(@NonNull Context context, @NonNull String name,
@NonNull CharSequence packageName, @NonNull List<String> mDNSNames) {
- mContext = Preconditions.checkNotNull(context, "context");
- mName = mContext.getResources().getIdentifier(Preconditions.checkStringNotEmpty(name,
- "name"), null, "com.android.printservice.recommendation");
- mPackageName = Preconditions.checkStringNotEmpty(packageName);
- mMDNSNames = new HashSet<>(Preconditions
- .checkCollectionNotEmpty(Preconditions.checkCollectionElementsNotNull(mDNSNames,
- "mDNSNames"), "mDNSNames"));
-
- mResolveQueue = NsdResolveQueue.getInstance();
- mPrinters = new HashSet<>();
+ mName = context.getResources().getIdentifier(name, null,
+ "com.android.printservice.recommendation");
+ mPackageName = packageName;
+ mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPES,
+ new VendorNameFilter(new HashSet<>(mDNSNames)));
}
@Override
@@ -96,18 +95,9 @@
return mPackageName;
}
- /**
- * @return The NDS manager
- */
- private NsdManager getNDSManager() {
- return (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
- }
-
@Override
public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
- mCallback = callback;
-
- DiscoveryListenerMultiplexer.addListener(getNDSManager(), PRINTER_SERVICE_TYPE, this);
+ mMDNSFilteredDiscovery.start(callback);
}
@Override
@@ -117,82 +107,6 @@
@Override
public void stop() throws Exception {
- mCallback.onChanged(0);
- mCallback = null;
-
- DiscoveryListenerMultiplexer.removeListener(getNDSManager(), this);
- }
-
- @Override
- public void onStartDiscoveryFailed(String serviceType, int errorCode) {
- Log.w(LOG_TAG, "Failed to start network discovery for type " + serviceType + ": "
- + errorCode);
- }
-
- @Override
- public void onStopDiscoveryFailed(String serviceType, int errorCode) {
- Log.w(LOG_TAG, "Failed to stop network discovery for type " + serviceType + ": "
- + errorCode);
- }
-
- @Override
- public void onDiscoveryStarted(String serviceType) {
- // empty
- }
-
- @Override
- public void onDiscoveryStopped(String serviceType) {
- mPrinters.clear();
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo serviceInfo) {
- mResolveQueue.resolve(getNDSManager(), serviceInfo,
- new NsdManager.ResolveListener() {
- @Override
- public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
- Log.w(LOG_TAG, "Service found: could not resolve " + serviceInfo + ": " +
- errorCode);
- }
-
- @Override
- public void onServiceResolved(NsdServiceInfo serviceInfo) {
- if (MDNSUtils.isVendorPrinter(serviceInfo, mMDNSNames)) {
- if (mCallback != null) {
- boolean added = mPrinters.add(serviceInfo.getHost().getHostAddress());
-
- if (added) {
- mCallback.onChanged(mPrinters.size());
- }
- }
- }
- }
- });
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo serviceInfo) {
- mResolveQueue.resolve(getNDSManager(), serviceInfo,
- new NsdManager.ResolveListener() {
- @Override
- public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
- Log.w(LOG_TAG, "Service lost: Could not resolve " + serviceInfo + ": "
- + errorCode);
- }
-
- @Override
- public void onServiceResolved(NsdServiceInfo serviceInfo) {
- if (MDNSUtils.isVendorPrinter(serviceInfo, mMDNSNames)) {
- if (mCallback != null) {
- boolean removed = mPrinters
- .remove(serviceInfo.getHost().getHostAddress());
-
- if (removed) {
- mCallback.onChanged(mPrinters.size());
- }
- }
- }
- }
- });
+ mMDNSFilteredDiscovery.stop();
}
}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/MDnsUtils.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/MDnsUtils.java
deleted file mode 100644
index 963e09b..0000000
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/MDnsUtils.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2016 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.printservice.recommendation.plugin.samsung;
-
-import android.net.nsd.NsdServiceInfo;
-import android.text.TextUtils;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Locale;
-import java.util.Map;
-
-public class MDnsUtils {
- public static final String ATTRIBUTE__TY = "ty";
- public static final String ATTRIBUTE__PRODUCT = "product";
- public static final String ATTRIBUTE__USB_MFG = "usb_MFG";
- public static final String ATTRIBUTE__MFG = "mfg";
-
- public static String getString(byte[] value) {
- if (value != null) return new String(value,StandardCharsets.UTF_8);
- return null;
- }
-
- public static boolean isVendorPrinter(NsdServiceInfo networkDevice, String[] vendorValues) {
-
- Map<String,byte[]> attributes = networkDevice.getAttributes();
- String product = getString(attributes.get(ATTRIBUTE__PRODUCT));
- String ty = getString(attributes.get(ATTRIBUTE__TY));
- String usbMfg = getString(attributes.get(ATTRIBUTE__USB_MFG));
- String mfg = getString(attributes.get(ATTRIBUTE__MFG));
- return containsVendor(product, vendorValues) || containsVendor(ty, vendorValues) || containsVendor(usbMfg, vendorValues) || containsVendor(mfg, vendorValues);
-
- }
-
- public static String getVendor(NsdServiceInfo networkDevice) {
- String vendor;
-
- Map<String,byte[]> attributes = networkDevice.getAttributes();
- vendor = getString(attributes.get(ATTRIBUTE__MFG));
- if (!TextUtils.isEmpty(vendor)) return vendor;
- vendor = getString(attributes.get(ATTRIBUTE__USB_MFG));
- if (!TextUtils.isEmpty(vendor)) return vendor;
-
- return null;
- }
-
- private static boolean containsVendor(String container, String[] vendorValues) {
- if ((container == null) || (vendorValues == null)) return false;
- for (String value : vendorValues) {
- if (containsString(container, value)
- || containsString(container.toLowerCase(Locale.US), value.toLowerCase(Locale.US))
- || containsString(container.toUpperCase(Locale.US), value.toUpperCase(Locale.US)))
- return true;
- }
- return false;
- }
-
- private static boolean containsString(String container, String contained) {
- return (container != null) && (contained != null) && (container.equalsIgnoreCase(contained) || container.contains(contained + " "));
- }
-}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
new file mode 100644
index 0000000..d03bb1d
--- /dev/null
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterMopria.java
@@ -0,0 +1,63 @@
+/*
+ * (c) Copyright 2016 Samsung Electronics
+ * (c) Copyright 2016 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.printservice.recommendation.plugin.samsung;
+
+import android.net.nsd.NsdServiceInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
+import com.android.printservice.recommendation.util.MDNSUtils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Printer filter for Mopria printer models supported by the print service plugin
+ */
+class PrinterFilterMopria implements MDNSFilteredDiscovery.PrinterFilter {
+ private static final String TAG = "PrinterFilterMopria";
+
+ static final Set<String> MOPRIA_MDNS_SERVICES = new HashSet<String>() {{
+ add("_ipp._tcp");
+ add("_ipps._tcp");
+ }};
+
+ private static final String PDL__PDF = "application/pdf";
+ private static final String PDL__PCLM = "application/PCLm";
+ private static final String PDL__PWG_RASTER = "image/pwg-raster";
+
+ private static final String PDL_ATTRIBUTE = "pdl";
+
+ @Override
+ public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
+ if (!MDNSUtils.isSupportedServiceType(nsdServiceInfo, MOPRIA_MDNS_SERVICES)) {
+ return false;
+ }
+
+ String pdls = MDNSUtils.getString(nsdServiceInfo.getAttributes().get(PDL_ATTRIBUTE));
+ boolean isMatch = !TextUtils.isEmpty(pdls)
+ && (pdls.contains(PDL__PDF)
+ || pdls.contains(PDL__PCLM)
+ || pdls.contains(PDL__PWG_RASTER));
+
+ if (isMatch) {
+ Log.d(TAG, "Mopria printer found: " + nsdServiceInfo.getServiceName());
+ }
+ return isMatch;
+ }
+}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
new file mode 100644
index 0000000..5b049ef
--- /dev/null
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterFilterSamsung.java
@@ -0,0 +1,117 @@
+/*
+ * (c) Copyright 2016 Samsung Electronics
+ * (c) Copyright 2016 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.printservice.recommendation.plugin.samsung;
+
+import android.net.nsd.NsdServiceInfo;
+import android.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
+import com.android.printservice.recommendation.util.MDNSUtils;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Printer filter for Samsung printer models supported by the print service plugin
+ */
+class PrinterFilterSamsung implements MDNSFilteredDiscovery.PrinterFilter {
+ private static final String TAG = "PrinterFilterSamsung";
+
+ static final Set<String> SAMSUNG_MDNS_SERVICES = new HashSet<String>() {{
+ add("_pdl-datastream._tcp");
+ }};
+
+ private static final String[] NOT_SUPPORTED_MODELS = new String[]{
+ "SCX-5x15",
+ "SF-555P",
+ "CF-555P",
+ "SCX-4x16",
+ "SCX-4214F",
+ "CLP-500",
+ "CJX-",
+ "MJC-"
+ };
+ private static final String ATTR_USB_MFG = "usb_MFG";
+ private static final String ATTR_MFG = "mfg";
+ private static final String ATTR_USB_MDL = "usb_MDL";
+ private static final String ATTR_MDL = "mdl";
+ private static final String ATTR_PRODUCT = "product";
+ private static final String ATTR_TY = "ty";
+
+ private static Set<String> SAMUNG_VENDOR_SET = new HashSet<String>() {{
+ add("samsung");
+ }};
+
+ @Override
+ public boolean matchesCriteria(NsdServiceInfo nsdServiceInfo) {
+ if (!MDNSUtils.isSupportedServiceType(nsdServiceInfo, SAMSUNG_MDNS_SERVICES)) {
+ return false;
+ }
+
+ if (!MDNSUtils.isVendorPrinter(nsdServiceInfo, SAMUNG_VENDOR_SET)) {
+ return false;
+ }
+
+ String modelName = getSamsungModelName(nsdServiceInfo);
+ if (modelName != null && isSupportedSamsungModel(modelName)) {
+ Log.d(TAG, "Samsung printer found: " + nsdServiceInfo.getServiceName());
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSupportedSamsungModel(String model) {
+ if (!TextUtils.isEmpty(model)) {
+ String modelToUpper = model.toUpperCase(Locale.US);
+ for (String unSupportedPrinter : NOT_SUPPORTED_MODELS) {
+ if (modelToUpper.contains(unSupportedPrinter)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private String getSamsungModelName(@NonNull NsdServiceInfo resolvedDevice) {
+ Map<String,byte[]> attributes = resolvedDevice.getAttributes();
+ String usb_mfg = MDNSUtils.getString(attributes.get(ATTR_USB_MFG));
+ if (TextUtils.isEmpty(usb_mfg)) {
+ usb_mfg = MDNSUtils.getString(attributes.get(ATTR_MFG));
+ }
+
+ String usb_mdl = MDNSUtils.getString(attributes.get(ATTR_USB_MDL));
+ if (TextUtils.isEmpty(usb_mdl)) {
+ usb_mdl = MDNSUtils.getString(attributes.get(ATTR_MDL));
+ }
+
+ String modelName;
+ if (!TextUtils.isEmpty(usb_mfg) && !TextUtils.isEmpty(usb_mdl)) {
+ modelName = usb_mfg.trim() + " " + usb_mdl.trim();
+ } else {
+ modelName = MDNSUtils.getString(attributes.get(ATTR_PRODUCT));
+ if (TextUtils.isEmpty(modelName)) {
+ modelName = MDNSUtils.getString(attributes.get(ATTR_TY));
+ }
+ }
+
+ return modelName;
+ }
+}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterHashMap.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterHashMap.java
deleted file mode 100644
index 032fe22..0000000
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/PrinterHashMap.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 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.printservice.recommendation.plugin.samsung;
-
-import android.net.nsd.NsdServiceInfo;
-
-import java.util.HashMap;
-
-final class PrinterHashMap extends HashMap<String, NsdServiceInfo> {
- public static String getKey(NsdServiceInfo serviceInfo) {
- return serviceInfo.getServiceName();
- }
- public NsdServiceInfo addPrinter(NsdServiceInfo device) {
- return put(getKey(device), device);
- }
- public NsdServiceInfo removePrinter(NsdServiceInfo device) {
- return remove(getKey(device));
- }
-}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
index e5b8a0f..eeb5122 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/SamsungRecommendationPlugin.java
@@ -1,102 +1,69 @@
-/*
-(c) Copyright 2016 Samsung Electronics..
-
-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.printservice.recommendation.plugin.samsung;
-
-import android.content.Context;
-import android.net.nsd.NsdServiceInfo;
-import android.text.TextUtils;
-
-import java.util.Locale;
-import java.util.Map;
-
-import com.android.printservice.recommendation.R;
-
-public class SamsungRecommendationPlugin extends ServiceRecommendationPlugin {
-
- private static final String TAG = "SamsungRecommendation";
-
- private static final String ATTR_USB_MFG = "usb_MFG";
- private static final String ATTR_MFG = "mfg";
- private static final String ATTR_USB_MDL = "usb_MDL";
- private static final String ATTR_MDL = "mdl";
- private static final String ATTR_PRODUCT = "product";
- private static final String ATTR_TY = "ty";
-
- private static String[] mNotSupportedDevices = new String[]{
- "SCX-5x15",
- "SF-555P",
- "CF-555P",
- "SCX-4x16",
- "SCX-4214F",
- "CLP-500",
- "CJX-",
- "MJC-"
- };
-
- private static boolean isSupportedModel(String model) {
- if (!TextUtils.isEmpty(model)) {
- String modelToUpper = model.toUpperCase(Locale.US);
- for (String unSupportedPrinter : mNotSupportedDevices) {
- if (modelToUpper.contains(unSupportedPrinter)) {
- return false;
- }
- }
- }
- return true;
- }
-
- public SamsungRecommendationPlugin(Context context) {
- super(context, R.string.plugin_vendor_samsung, new VendorInfo(context.getResources(), R.array.known_print_vendor_info_for_samsung), new String[]{"_pdl-datastream._tcp"});
- }
-
- @Override
- public boolean matchesCriteria(String vendor, NsdServiceInfo nsdServiceInfo) {
- if (!TextUtils.equals(vendor, mVendorInfo.mVendorID)) return false;
-
- String modelName = getModelName(nsdServiceInfo);
- if (modelName != null) {
- return (isSupportedModel(modelName));
- }
- return false;
- }
-
- private String getModelName(NsdServiceInfo resolvedDevice) {
- Map<String,byte[]> attributes = resolvedDevice.getAttributes();
- String usb_mfg = MDnsUtils.getString(attributes.get(ATTR_USB_MFG));
- if (TextUtils.isEmpty(usb_mfg)) {
- usb_mfg = MDnsUtils.getString(attributes.get(ATTR_MFG));
- }
-
- String usb_mdl = MDnsUtils.getString(attributes.get(ATTR_USB_MDL));
- if (TextUtils.isEmpty(usb_mdl)) {
- usb_mdl = MDnsUtils.getString(attributes.get(ATTR_MDL));
- }
-
- String modelName = null;
- if (!TextUtils.isEmpty(usb_mfg) && !TextUtils.isEmpty(usb_mdl)) {
- modelName = usb_mfg.trim() + " " + usb_mdl.trim();
- } else {
- modelName = MDnsUtils.getString(attributes.get(ATTR_PRODUCT));
- if (TextUtils.isEmpty(modelName)) {
- modelName = MDnsUtils.getString(attributes.get(ATTR_TY));
- }
- }
-
- return modelName;
- }
-}
+/*
+ * (c) Copyright 2016 Samsung Electronics
+ * (c) Copyright 2016 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.printservice.recommendation.plugin.samsung;
+
+import android.content.Context;
+import android.net.nsd.NsdServiceInfo;
+import android.annotation.NonNull;
+
+import com.android.printservice.recommendation.PrintServicePlugin;
+import com.android.printservice.recommendation.R;
+import com.android.printservice.recommendation.util.MDNSFilteredDiscovery;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class SamsungRecommendationPlugin implements PrintServicePlugin {
+ private static final Set<String> ALL_MDNS_SERVICES = new HashSet<String>() {{
+ addAll(PrinterFilterMopria.MOPRIA_MDNS_SERVICES);
+ addAll(PrinterFilterSamsung.SAMSUNG_MDNS_SERVICES);
+ }};
+
+ private final @NonNull Context mContext;
+ private final @NonNull MDNSFilteredDiscovery mMDNSFilteredDiscovery;
+
+ private final @NonNull PrinterFilterSamsung mPrinterFilterSamsung = new PrinterFilterSamsung();
+ private final @NonNull PrinterFilterMopria mPrinterFilterMopria = new PrinterFilterMopria();
+
+ public SamsungRecommendationPlugin(@NonNull Context context) {
+ mContext = context;
+ mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, ALL_MDNS_SERVICES,
+ (NsdServiceInfo nsdServiceInfo) ->
+ mPrinterFilterSamsung.matchesCriteria(nsdServiceInfo) ||
+ mPrinterFilterMopria.matchesCriteria(nsdServiceInfo));
+ }
+
+ @Override
+ public int getName() {
+ return R.string.plugin_vendor_samsung;
+ }
+
+ @Override
+ public @NonNull CharSequence getPackageName() {
+ return mContext.getString(R.string.plugin_package_samsung);
+ }
+
+ @Override
+ public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
+ mMDNSFilteredDiscovery.start(callback);
+ }
+
+ @Override
+ public void stop() throws Exception {
+ mMDNSFilteredDiscovery.stop();
+ }
+}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceListener.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceListener.java
deleted file mode 100644
index 7bb83c9..0000000
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceListener.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2016 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.printservice.recommendation.plugin.samsung;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.text.TextUtils;
-import android.util.Pair;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import com.android.printservice.recommendation.R;
-import com.android.printservice.recommendation.util.DiscoveryListenerMultiplexer;
-
-public class ServiceListener implements ServiceResolveQueue.ResolveCallback {
-
- private final NsdManager mNSDManager;
- private final Map<String, VendorInfo> mVendorInfoHashMap;
- private final String[] mServiceType;
- private final Observer mObserver;
- private final ServiceResolveQueue mResolveQueue;
- private List<NsdManager.DiscoveryListener> mListeners = new ArrayList<>();
- public HashMap<String, PrinterHashMap> mVendorHashMap = new HashMap<>();
-
- public interface Observer {
- boolean matchesCriteria(String vendor, NsdServiceInfo serviceInfo);
- void dataSetChanged();
- }
-
- public ServiceListener(Context context, Observer observer, String[] serviceTypes) {
- mObserver = observer;
- mServiceType = serviceTypes;
- mNSDManager = (NsdManager)context.getSystemService(Context.NSD_SERVICE);
- mResolveQueue = ServiceResolveQueue.getInstance(mNSDManager);
-
- Map<String, VendorInfo> vendorInfoMap = new HashMap<>();
- TypedArray testArray = context.getResources().obtainTypedArray(R.array.known_print_plugin_vendors);
- for(int i = 0; i < testArray.length(); i++) {
- int arrayID = testArray.getResourceId(i, 0);
- if (arrayID != 0) {
- VendorInfo info = new VendorInfo(context.getResources(), arrayID);
- vendorInfoMap.put(info.mVendorID, info);
- vendorInfoMap.put(info.mPackageName, info);
- }
- }
- testArray.recycle();
- mVendorInfoHashMap = vendorInfoMap;
- }
-
- @Override
- public void serviceResolved(NsdServiceInfo nsdServiceInfo) {
- printerFound(nsdServiceInfo);
- }
-
- private synchronized void printerFound(NsdServiceInfo nsdServiceInfo) {
- if (nsdServiceInfo == null) return;
- if (TextUtils.isEmpty(PrinterHashMap.getKey(nsdServiceInfo))) return;
- String vendor = MDnsUtils.getVendor(nsdServiceInfo);
- if (vendor == null) vendor = "";
- for(Map.Entry<String,VendorInfo> entry : mVendorInfoHashMap.entrySet()) {
- for(String vendorValues : entry.getValue().mDNSValues) {
- if (vendor.equalsIgnoreCase(vendorValues)) {
- vendor = entry.getValue().mVendorID;
- break;
- }
- }
- // intentional pointer check
- //noinspection StringEquality
- if ((vendor != entry.getValue().mVendorID) &&
- MDnsUtils.isVendorPrinter(nsdServiceInfo, entry.getValue().mDNSValues)) {
- vendor = entry.getValue().mVendorID;
- }
- // intentional pointer check
- //noinspection StringEquality
- if (vendor == entry.getValue().mVendorID) break;
- }
-
- if (TextUtils.isEmpty(vendor)) {
- return;
- }
-
- if (!mObserver.matchesCriteria(vendor, nsdServiceInfo))
- return;
- boolean mapsChanged;
-
- PrinterHashMap vendorHash = mVendorHashMap.get(vendor);
- if (vendorHash == null) {
- vendorHash = new PrinterHashMap();
- }
- mapsChanged = (vendorHash.addPrinter(nsdServiceInfo) == null);
- mVendorHashMap.put(vendor, vendorHash);
-
- if (mapsChanged) {
- mObserver.dataSetChanged();
- }
- }
-
- private synchronized void printerRemoved(NsdServiceInfo nsdServiceInfo) {
- boolean wasRemoved = false;
- Set<String> vendors = mVendorHashMap.keySet();
- for(String vendor : vendors) {
- PrinterHashMap map = mVendorHashMap.get(vendor);
- wasRemoved |= (map.removePrinter(nsdServiceInfo) != null);
- if (map.isEmpty()) wasRemoved |= (mVendorHashMap.remove(vendor) != null);
- }
- if (wasRemoved) {
- mObserver.dataSetChanged();
- }
- }
-
- public void start() {
- stop();
- for(final String service :mServiceType) {
- NsdManager.DiscoveryListener listener = new NsdManager.DiscoveryListener() {
- @Override
- public void onStartDiscoveryFailed(String s, int i) {
-
- }
-
- @Override
- public void onStopDiscoveryFailed(String s, int i) {
-
- }
-
- @Override
- public void onDiscoveryStarted(String s) {
-
- }
-
- @Override
- public void onDiscoveryStopped(String s) {
-
- }
-
- @Override
- public void onServiceFound(NsdServiceInfo nsdServiceInfo) {
- mResolveQueue.queueRequest(nsdServiceInfo, ServiceListener.this);
- }
-
- @Override
- public void onServiceLost(NsdServiceInfo nsdServiceInfo) {
- mResolveQueue.removeRequest(nsdServiceInfo, ServiceListener.this);
- printerRemoved(nsdServiceInfo);
- }
- };
- DiscoveryListenerMultiplexer.addListener(mNSDManager, service, listener);
- mListeners.add(listener);
- }
- }
-
- public void stop() {
- for(NsdManager.DiscoveryListener listener : mListeners) {
- DiscoveryListenerMultiplexer.removeListener(mNSDManager, listener);
- }
- mVendorHashMap.clear();
- mListeners.clear();
- }
-
- public Pair<Integer, Integer> getCount() {
- int count = 0;
- for (PrinterHashMap map : mVendorHashMap.values()) {
- count += map.size();
- }
- return Pair.create(mVendorHashMap.size(), count);
- }
-}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceRecommendationPlugin.java
deleted file mode 100644
index 9d15f30..0000000
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceRecommendationPlugin.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 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.printservice.recommendation.plugin.samsung;
-
-import android.content.Context;
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.annotation.NonNull;
-import android.text.TextUtils;
-import com.android.printservice.recommendation.PrintServicePlugin;
-
-public abstract class ServiceRecommendationPlugin implements PrintServicePlugin, ServiceListener.Observer {
-
- protected static final String PDL_ATTRIBUTE = "pdl";
-
- protected final Object mLock = new Object();
- protected PrinterDiscoveryCallback mCallback = null;
- protected final ServiceListener mListener;
- protected final NsdManager mNSDManager;
- protected final VendorInfo mVendorInfo;
- private final int mVendorStringID;
-
- protected ServiceRecommendationPlugin(Context context, int vendorStringID, VendorInfo vendorInfo, String[] services) {
- mNSDManager = (NsdManager)context.getSystemService(Context.NSD_SERVICE);
- mVendorStringID = vendorStringID;
- mVendorInfo = vendorInfo;
- mListener = new ServiceListener(context, this, services);
- }
-
- @Override
- public int getName() {
- return mVendorStringID;
- }
-
- @NonNull
- @Override
- public CharSequence getPackageName() {
- return mVendorInfo.mPackageName;
- }
-
- @Override
- public void start(@NonNull PrinterDiscoveryCallback callback) throws Exception {
- synchronized (mLock) {
- mCallback = callback;
- }
- mListener.start();
- }
-
- @Override
- public void stop() throws Exception {
- synchronized (mLock) {
- mCallback = null;
- }
- mListener.stop();
- }
-
- @Override
- public void dataSetChanged() {
- synchronized (mLock) {
- if (mCallback != null) mCallback.onChanged(getCount());
- }
- }
-
- @Override
- public boolean matchesCriteria(String vendor, NsdServiceInfo nsdServiceInfo) {
- return TextUtils.equals(vendor, mVendorInfo.mVendorID);
- }
-
- public int getCount() {
- return mListener.getCount().second;
- }
-}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceResolveQueue.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceResolveQueue.java
deleted file mode 100644
index e5691b7..0000000
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/ServiceResolveQueue.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2016 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.printservice.recommendation.plugin.samsung;
-
-import android.net.nsd.NsdManager;
-import android.net.nsd.NsdServiceInfo;
-import android.util.Pair;
-import com.android.printservice.recommendation.util.NsdResolveQueue;
-
-import java.util.LinkedList;
-
-final class ServiceResolveQueue {
-
- private final NsdManager mNsdManager;
- private final LinkedList<Pair<NsdServiceInfo, ResolveCallback>> mQueue = new LinkedList<>();
- private final Object mLock = new Object();
-
- private static Object sLock = new Object();
- private static ServiceResolveQueue sInstance = null;
- private final NsdResolveQueue mNsdResolveQueue;
- private Pair<NsdServiceInfo, ResolveCallback> mCurrentRequest = null;
-
- public static void createInstance(NsdManager nsdManager) {
- if (sInstance == null) sInstance = new ServiceResolveQueue(nsdManager);
- }
-
- public static ServiceResolveQueue getInstance(NsdManager nsdManager) {
- synchronized (sLock) {
- createInstance(nsdManager);
- return sInstance;
- }
- }
-
- public static void destroyInstance() {
- sInstance = null;
- }
-
- public interface ResolveCallback {
- void serviceResolved(NsdServiceInfo nsdServiceInfo);
- }
-
- public ServiceResolveQueue(NsdManager nsdManager) {
- mNsdManager = nsdManager;
- mNsdResolveQueue = NsdResolveQueue.getInstance();
- }
-
- public void queueRequest(NsdServiceInfo serviceInfo, ResolveCallback callback) {
- synchronized (mLock) {
- Pair<NsdServiceInfo, ResolveCallback> newRequest = Pair.create(serviceInfo, callback);
- if (mQueue.contains(newRequest)) return;
- mQueue.add(newRequest);
- makeNextRequest();
- }
- }
-
- public void removeRequest(NsdServiceInfo serviceInfo, ResolveCallback callback) {
- synchronized (mLock) {
- Pair<NsdServiceInfo, ResolveCallback> newRequest = Pair.create(serviceInfo, callback);
- mQueue.remove(newRequest);
- if ((mCurrentRequest != null) && newRequest.equals(mCurrentRequest)) mCurrentRequest = null;
- }
- }
-
- private void makeNextRequest() {
- synchronized (mLock) {
- if (mCurrentRequest != null) return;
- if (mQueue.isEmpty()) return;
- mCurrentRequest = mQueue.removeFirst();
- mNsdResolveQueue.resolve(mNsdManager, mCurrentRequest.first,
- new NsdManager.ResolveListener() {
- @Override
- public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int i) {
- synchronized (mLock) {
- if (mCurrentRequest != null) mQueue.add(mCurrentRequest);
- makeNextRequest();
- }
- }
-
- @Override
- public void onServiceResolved(NsdServiceInfo nsdServiceInfo) {
- synchronized (mLock) {
- if (mCurrentRequest != null) {
- mCurrentRequest.second.serviceResolved(nsdServiceInfo);
- mCurrentRequest = null;
- }
- makeNextRequest();
- }
- }
- });
-
- }
- }
-
-
-}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/VendorInfo.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/VendorInfo.java
deleted file mode 100644
index 0ebb4e4..0000000
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/samsung/VendorInfo.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 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.printservice.recommendation.plugin.samsung;
-
-import android.content.res.Resources;
-
-import java.util.Arrays;
-
-public final class VendorInfo {
-
- public final String mPackageName;
- public final String mVendorID;
- public final String[] mDNSValues;
- public final int mID;
-
- public VendorInfo(Resources resources, int vendor_info_id) {
- mID = vendor_info_id;
- String[] data = resources.getStringArray(vendor_info_id);
- if ((data == null) || (data.length < 2)) {
- data = new String[] { null, null };
- }
- mPackageName = data[0];
- mVendorID = data[1];
- mDNSValues = (data.length > 2) ? Arrays.copyOfRange(data, 2, data.length) : new String[]{};
- }
-}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSFilteredDiscovery.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSFilteredDiscovery.java
new file mode 100644
index 0000000..c5dbc8c
--- /dev/null
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSFilteredDiscovery.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 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.printservice.recommendation.util;
+
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+import com.android.printservice.recommendation.PrintServicePlugin;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A discovery listening for mDNS results and only adding the ones that {@link
+ * PrinterFilter#matchesCriteria match} configured list
+ */
+public class MDNSFilteredDiscovery implements NsdManager.DiscoveryListener {
+ private static final String LOG_TAG = "MDNSFilteredDiscovery";
+
+ /**
+ * mDNS service filter interface.
+ * Implement {@link PrinterFilter#matchesCriteria} to filter out supported services
+ */
+ public interface PrinterFilter {
+ /**
+ * Main filter method. Should return true if mDNS service is supported
+ * by the print service plugin
+ *
+ * @param nsdServiceInfo The service info to check
+ *
+ * @return True if service is supported by the print service plugin
+ */
+ boolean matchesCriteria(NsdServiceInfo nsdServiceInfo);
+ }
+
+ /** Printer identifiers of the mPrinters found. */
+ @GuardedBy("mLock")
+ private final @NonNull HashSet<String> mPrinters;
+
+ /** Service types discovered by this plugin */
+ private final @NonNull HashSet<String> mServiceTypes;
+
+ /** Context of the user of this plugin */
+ private final @NonNull Context mContext;
+
+ /** mDNS services filter */
+ private final @NonNull PrinterFilter mPrinterFilter;
+
+ /**
+ * Call back to report the number of mPrinters found.
+ *
+ * We assume that {@link #start} and {@link #stop} are never called in parallel, hence it is
+ * safe to not synchronize access to this field.
+ */
+ private @Nullable PrintServicePlugin.PrinterDiscoveryCallback mCallback;
+
+ /** Queue used to resolve nsd infos */
+ private final @NonNull NsdResolveQueue mResolveQueue;
+
+ /**
+ * Create new stub that assumes that a print service can be used to print on all mPrinters
+ * matching some mDNS names.
+ *
+ * @param context The context the plugin runs in
+ * @param serviceTypes The mDNS service types to listen to.
+ * @param printerFilter The filter for mDNS services
+ */
+ public MDNSFilteredDiscovery(@NonNull Context context,
+ @NonNull Set<String> serviceTypes,
+ @NonNull PrinterFilter printerFilter) {
+ mContext = Preconditions.checkNotNull(context, "context");
+ mServiceTypes = new HashSet<>(Preconditions
+ .checkCollectionNotEmpty(Preconditions.checkCollectionElementsNotNull(serviceTypes,
+ "serviceTypes"), "serviceTypes"));
+ mPrinterFilter = Preconditions.checkNotNull(printerFilter, "printerFilter");
+
+ mResolveQueue = NsdResolveQueue.getInstance();
+ mPrinters = new HashSet<>();
+ }
+
+ /**
+ * @return The NDS manager
+ */
+ private NsdManager getNDSManager() {
+ return (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
+ }
+
+ /**
+ * Start the discovery.
+ *
+ * @param callback Callbacks used by this plugin.
+ */
+ public void start(@NonNull PrintServicePlugin.PrinterDiscoveryCallback callback) {
+ mCallback = callback;
+ mCallback.onChanged(mPrinters.size());
+
+ for (String serviceType : mServiceTypes) {
+ DiscoveryListenerMultiplexer.addListener(getNDSManager(), serviceType, this);
+ }
+ }
+
+ /**
+ * Stop the discovery. This can only return once the plugin is completely finished and cleaned up.
+ */
+ public void stop() {
+ mCallback.onChanged(0);
+ mCallback = null;
+
+ for (int i = 0; i < mServiceTypes.size(); ++i) {
+ DiscoveryListenerMultiplexer.removeListener(getNDSManager(), this);
+ }
+ }
+
+ /**
+ *
+ * @return The number of discovered printers
+ */
+ public int getCount() {
+ return mPrinters.size();
+ }
+
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ Log.w(LOG_TAG, "Failed to start network discovery for type " + serviceType + ": "
+ + errorCode);
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ Log.w(LOG_TAG, "Failed to stop network discovery for type " + serviceType + ": "
+ + errorCode);
+ }
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {
+ // empty
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ mPrinters.clear();
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ mResolveQueue.resolve(getNDSManager(), serviceInfo,
+ new NsdManager.ResolveListener() {
+ @Override
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ Log.w(LOG_TAG, "Service found: could not resolve " + serviceInfo + ": " +
+ errorCode);
+ }
+
+ @Override
+ public void onServiceResolved(NsdServiceInfo serviceInfo) {
+ if (mPrinterFilter.matchesCriteria(serviceInfo)) {
+ if (mCallback != null) {
+ boolean added = mPrinters.add(serviceInfo.getHost().getHostAddress());
+ if (added) {
+ mCallback.onChanged(mPrinters.size());
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ mResolveQueue.resolve(getNDSManager(), serviceInfo,
+ new NsdManager.ResolveListener() {
+ @Override
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ Log.w(LOG_TAG, "Service lost: Could not resolve " + serviceInfo + ": "
+ + errorCode);
+ }
+
+ @Override
+ public void onServiceResolved(NsdServiceInfo serviceInfo) {
+ if (mPrinterFilter.matchesCriteria(serviceInfo)) {
+ if (mCallback != null) {
+ boolean removed = mPrinters
+ .remove(serviceInfo.getHost().getHostAddress());
+
+ if (removed) {
+ mCallback.onChanged(mPrinters.size());
+ }
+ }
+ }
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSUtils.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSUtils.java
similarity index 75%
rename from packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSUtils.java
rename to packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSUtils.java
index 4c27a47..a6df3c8 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSUtils.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/util/MDNSUtils.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package com.android.printservice.recommendation.plugin.mdnsFilter;
+package com.android.printservice.recommendation.util;
import android.annotation.NonNull;
import android.net.nsd.NsdServiceInfo;
@@ -27,12 +27,15 @@
/**
* Utils for dealing with mDNS attributes
*/
-class MDNSUtils {
+public class MDNSUtils {
public static final String ATTRIBUTE_TY = "ty";
public static final String ATTRIBUTE_PRODUCT = "product";
public static final String ATTRIBUTE_USB_MFG = "usb_mfg";
public static final String ATTRIBUTE_MFG = "mfg";
+ private MDNSUtils() {
+ }
+
/**
* Check if the service has any of a set of vendor names.
*
@@ -95,4 +98,35 @@
private static boolean containsString(@NonNull String container, @NonNull String contained) {
return container.equalsIgnoreCase(contained) || container.contains(contained + " ");
}
+
+ /**
+ * Return String from mDNS attribute byte array
+ *
+ * @param value the byte array with string data
+ *
+ * @return constructed string
+ */
+ public static String getString(byte[] value) {
+ if (value != null) return new String(value, StandardCharsets.UTF_8);
+ return null;
+ }
+
+ /**
+ * Check if service has a type of supported types set
+ *
+ * @param serviceInfo The service
+ * @param serviceTypes The supported service types set
+ *
+ * @return true if service has a type of supported types set
+ */
+ public static boolean isSupportedServiceType(@NonNull NsdServiceInfo serviceInfo,
+ @NonNull Set<String> serviceTypes) {
+ String curType = serviceInfo.getServiceType().toLowerCase();
+ for (String type : serviceTypes) {
+ if (curType.contains(type.toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index bb85de2..7206127 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -51,6 +51,7 @@
<bool name="def_wifi_on">false</bool>
<!-- 0 == never, 1 == only when plugged in, 2 == always -->
<integer name="def_wifi_sleep_policy">2</integer>
+ <bool name="def_wifi_wakeup_enabled">false</bool>
<bool name="def_networks_available_notification_on">true</bool>
<bool name="def_backup_enabled">false</bool>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
index 09879d8..f653371 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
@@ -44,7 +44,7 @@
void abortCurrentGesture();
- void setLandscape(boolean landscape);
+ void setVertical(boolean vertical);
void setCarMode(boolean carMode);
}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a6604cb..29b5b62 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -129,9 +129,6 @@
<dimen name="navigation_key_padding">0dp</dimen>
- <dimen name="navigation_key_width_sw600dp_land">162dp</dimen>
- <dimen name="navigation_key_padding_sw600dp_land">42dp</dimen>
-
<!-- The width of the view containing the menu/ime navigation bar icons -->
<dimen name="navigation_extra_key_width">36dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index bfc26e3..af1fc59 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1126,6 +1126,7 @@
<item></item> <!-- STREAM_SYSTEM_ENFORCED -->
<item></item> <!-- STREAM_DTMF -->
<item></item> <!-- STREAM_TTS -->
+ <item>Accessibility</item> <!-- STREAM_ACCESSIBILITY -->
</string-array>
<string name="volume_stream_muted" translatable="false">%s silent</string>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 07e1193..c19e806 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1798,7 +1798,6 @@
// (like recents). Temporary enable/disable (e.g. the "back" button) are
// done in KeyguardHostView.
flags |= StatusBarManager.DISABLE_RECENT;
- flags |= StatusBarManager.DISABLE_SEARCH;
}
if (isShowingAndNotOccluded()) {
flags |= StatusBarManager.DISABLE_HOME;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index f6fe176..c9f7456 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -40,6 +40,7 @@
private int mImageResource = -1;
private Drawable mImageDrawable;
private View mCurrentView;
+ private boolean mVertical;
public ButtonDispatcher(int id) {
mId = id;
@@ -49,13 +50,6 @@
mViews.clear();
}
- void addView(View view, boolean landscape) {
- addView(view);
- if (view instanceof ButtonInterface) {
- ((ButtonInterface) view).setLandscape(landscape);
- }
- }
-
void addView(View view) {
mViews.add(view);
view.setOnClickListener(mClickListener);
@@ -75,6 +69,10 @@
} else if (mImageDrawable != null) {
((ButtonInterface) view).setImageDrawable(mImageDrawable);
}
+
+ if (view instanceof ButtonInterface) {
+ ((ButtonInterface) view).setVertical(mVertical);
+ }
}
public int getId() {
@@ -186,4 +184,15 @@
}
}
+ public void setVertical(boolean vertical) {
+ mVertical = vertical;
+ final int N = mViews.size();
+ for (int i = 0; i < N; i++) {
+ final View view = mViews.get(i);
+ if (view instanceof ButtonInterface) {
+ ((ButtonInterface) view).setVertical(vertical);
+ }
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index c420927..b2b093c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -17,12 +17,14 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.SparseArray;
+import android.view.Display;
+import android.view.Display.Mode;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Space;
@@ -72,12 +74,13 @@
protected FrameLayout mRot0;
protected FrameLayout mRot90;
+ private boolean isRot0Landscape;
private SparseArray<ButtonDispatcher> mButtonDispatchers;
private String mCurrentLayout;
- private View mLastRot0;
- private View mLastRot90;
+ private View mLastPortrait;
+ private View mLastLandscape;
private boolean mAlternativeOrder;
@@ -85,6 +88,10 @@
super(context, attrs);
mDensity = context.getResources().getConfiguration().densityDpi;
createInflaters();
+ Display display = ((WindowManager)
+ context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
+ Mode displayMode = display.getMode();
+ isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
}
private void createInflaters() {
@@ -215,17 +222,17 @@
String[] center = sets[1].split(BUTTON_SEPARATOR);
String[] end = sets[2].split(BUTTON_SEPARATOR);
// Inflate these in start to end order or accessibility traversal will be messed up.
- inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
- inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
+ inflateButtons(start, (ViewGroup) mRot0.findViewById(R.id.ends_group), isRot0Landscape);
+ inflateButtons(start, (ViewGroup) mRot90.findViewById(R.id.ends_group), !isRot0Landscape);
- inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group), false);
- inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group), true);
+ inflateButtons(center, (ViewGroup) mRot0.findViewById(R.id.center_group), isRot0Landscape);
+ inflateButtons(center, (ViewGroup) mRot90.findViewById(R.id.center_group), !isRot0Landscape);
addGravitySpacer((LinearLayout) mRot0.findViewById(R.id.ends_group));
addGravitySpacer((LinearLayout) mRot90.findViewById(R.id.ends_group));
- inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.ends_group), false);
- inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.ends_group), true);
+ inflateButtons(end, (ViewGroup) mRot0.findViewById(R.id.ends_group), isRot0Landscape);
+ inflateButtons(end, (ViewGroup) mRot90.findViewById(R.id.ends_group), !isRot0Landscape);
}
private void addGravitySpacer(LinearLayout layout) {
@@ -234,7 +241,7 @@
private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape) {
for (int i = 0; i < buttons.length; i++) {
- inflateButton(buttons[i], parent, landscape, i);
+ inflateButton(buttons[i], parent, landscape);
}
}
@@ -247,8 +254,7 @@
}
@Nullable
- protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
- int indexInParent) {
+ protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape) {
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
float size = extractSize(buttonSpec);
View v = createView(buttonSpec, parent, inflater, landscape);
@@ -259,15 +265,15 @@
params.width = (int) (params.width * size);
}
parent.addView(v);
- addToDispatchers(v, landscape);
- View lastView = landscape ? mLastRot90 : mLastRot0;
+ addToDispatchers(v);
+ View lastView = landscape ? mLastLandscape : mLastPortrait;
if (lastView != null) {
v.setAccessibilityTraversalAfter(lastView.getId());
}
if (landscape) {
- mLastRot90 = v;
+ mLastLandscape = v;
} else {
- mLastRot0 = v;
+ mLastPortrait = v;
}
return v;
}
@@ -283,19 +289,10 @@
}
if (HOME.equals(button)) {
v = inflater.inflate(R.layout.home, parent, false);
- if (landscape && isSw600Dp()) {
- setupLandButton(v);
- }
} else if (BACK.equals(button)) {
v = inflater.inflate(R.layout.back, parent, false);
- if (landscape && isSw600Dp()) {
- setupLandButton(v);
- }
} else if (RECENT.equals(button)) {
v = inflater.inflate(R.layout.recent_apps, parent, false);
- if (landscape && isSw600Dp()) {
- setupLandButton(v);
- }
} else if (MENU_IME.equals(button)) {
v = inflater.inflate(R.layout.menu_ime, parent, false);
} else if (NAVSPACE.equals(button)) {
@@ -348,37 +345,22 @@
return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START));
}
- private void addToDispatchers(View v, boolean landscape) {
+ private void addToDispatchers(View v) {
if (mButtonDispatchers != null) {
final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId());
if (indexOfKey >= 0) {
- mButtonDispatchers.valueAt(indexOfKey).addView(v, landscape);
+ mButtonDispatchers.valueAt(indexOfKey).addView(v);
} else if (v instanceof ViewGroup) {
final ViewGroup viewGroup = (ViewGroup)v;
final int N = viewGroup.getChildCount();
for (int i = 0; i < N; i++) {
- addToDispatchers(viewGroup.getChildAt(i), landscape);
+ addToDispatchers(viewGroup.getChildAt(i));
}
}
}
}
- private boolean isSw600Dp() {
- Configuration configuration = mContext.getResources().getConfiguration();
- return (configuration.smallestScreenWidthDp >= 600);
- }
- /**
- * This manually sets the width of sw600dp landscape buttons because despite
- * overriding the configuration from the overridden resources aren't loaded currently.
- */
- private void setupLandButton(View v) {
- Resources res = mContext.getResources();
- v.getLayoutParams().width = res.getDimensionPixelOffset(
- R.dimen.navigation_key_width_sw600dp_land);
- int padding = res.getDimensionPixelOffset(R.dimen.navigation_key_padding_sw600dp_land);
- v.setPadding(padding, v.getPaddingTop(), padding, v.getPaddingBottom());
- }
private void clearViews() {
if (mButtonDispatchers != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index a0ea9bf..7023324 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -590,6 +590,8 @@
updateTaskSwitchHelper();
setNavigationIconHints(mNavigationIconHints, true);
+
+ getHomeButton().setVertical(mVertical);
}
public void onKeyguardOccludedChanged(boolean keyguardOccluded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index bcc5a3f..ae59315 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -275,7 +275,7 @@
}
@Override
- public void setLandscape(boolean landscape) {
+ public void setVertical(boolean vertical) {
//no op
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index ec0e4cd..bb5632b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -78,6 +78,7 @@
AudioSystem.STREAM_SYSTEM_ENFORCED,
AudioSystem.STREAM_TTS,
AudioSystem.STREAM_VOICE_CALL,
+ AudioSystem.STREAM_ACCESSIBILITY,
};
private final HandlerThread mWorkerThread;
@@ -119,7 +120,26 @@
mObserver = new SettingObserver(mWorker);
mObserver.init();
mReceiver.init();
- mStreamTitles = mContext.getResources().getStringArray(R.array.volume_stream_titles);
+ final String[] titles =
+ mContext.getResources().getStringArray(R.array.volume_stream_titles);
+ if (STREAMS.length == titles.length) {
+ mStreamTitles = titles;
+ } else if (STREAMS.length > titles.length) {
+ Log.e(TAG, String.format("Missing stream titles (found %d, expected %d): "
+ + " invalid resources for volume_stream_titles",
+ titles.length, STREAMS.length));
+ mStreamTitles = new String[STREAMS.length];
+ System.arraycopy(titles, 0, mStreamTitles, 0, titles.length);
+ for (int i = titles.length ; i < STREAMS.length ; i++) {
+ mStreamTitles[i] = "";
+ }
+ } else { // STREAMS.length < titles.length
+ Log.e(TAG, String.format("Too many stream titles (found %d, expected %d): "
+ + " invalid resources for volume_stream_titles",
+ titles.length, STREAMS.length));
+ mStreamTitles = new String[STREAMS.length];
+ System.arraycopy(titles, 0, mStreamTitles, 0, STREAMS.length);
+ }
mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = mVibrator != null && mVibrator.hasVibrator();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ab111a0..df71ced 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2843,15 +2843,8 @@
}
if (mMotionEventInjector != null) {
List<GestureDescription.GestureStep> steps = gestureSteps.getList();
- List<MotionEvent> events = GestureDescription.MotionEventGenerator
- .getMotionEventsFromGestureSteps(steps);
- // Confirm that the motion events end with an UP event.
- if (events.get(events.size() - 1).getAction() == MotionEvent.ACTION_UP) {
- mMotionEventInjector.injectEvents(events, mServiceInterface, sequence);
- return;
- } else {
- Slog.e(LOG_TAG, "Gesture is not well-formed");
- }
+ mMotionEventInjector.injectEvents(steps, mServiceInterface, sequence);
+ return;
} else {
Slog.e(LOG_TAG, "MotionEventInjector installation timed out");
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
index 8042ddb..48041ad 100644
--- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
+++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
@@ -16,49 +16,67 @@
package com.android.server.accessibility;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.GestureStep;
+import android.accessibilityservice.GestureDescription.TouchPoint;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import com.android.internal.os.SomeArgs;
-import com.android.server.accessibility.AccessibilityManagerService.Service;
+import java.util.ArrayList;
import java.util.List;
/**
* Injects MotionEvents to permit {@code AccessibilityService}s to touch the screen on behalf of
* users.
- *
+ * <p>
* All methods except {@code injectEvents} must be called only from the main thread.
*/
public class MotionEventInjector implements EventStreamTransformation, Handler.Callback {
private static final String LOG_TAG = "MotionEventInjector";
private static final int MESSAGE_SEND_MOTION_EVENT = 1;
private static final int MESSAGE_INJECT_EVENTS = 2;
- private static final int MAX_POINTERS = 11; // Non-binding maximum
+
+ /**
+ * Constants used to initialize all MotionEvents
+ */
+ private static final int EVENT_META_STATE = 0;
+ private static final int EVENT_BUTTON_STATE = 0;
+ private static final int EVENT_DEVICE_ID = 0;
+ private static final int EVENT_EDGE_FLAGS = 0;
+ private static final int EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN;
+ private static final int EVENT_FLAGS = 0;
+ private static final float EVENT_X_PRECISION = 1;
+ private static final float EVENT_Y_PRECISION = 1;
+
+ private static MotionEvent.PointerCoords[] sPointerCoords;
+ private static MotionEvent.PointerProperties[] sPointerProps;
private final Handler mHandler;
private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
- // These two arrays must be the same length
- private MotionEvent.PointerProperties[] mPointerProperties =
- new MotionEvent.PointerProperties[MAX_POINTERS];
- private MotionEvent.PointerCoords[] mPointerCoords =
- new MotionEvent.PointerCoords[MAX_POINTERS];
private EventStreamTransformation mNext;
private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
- private int mSequenceForCurrentGesture;
- private int mSourceOfInjectedGesture = InputDevice.SOURCE_UNKNOWN;
+ private IntArray mSequencesInProgress = new IntArray(5);
private boolean mIsDestroyed = false;
+ private TouchPoint[] mLastTouchPoints;
+ private int mNumLastTouchPoints;
+ private long mDownTime;
+ private long mLastScheduledEventTime;
+ private SparseIntArray mStrokeIdToPointerId = new SparseIntArray(5);
/**
* @param looper A looper on the main thread to use for dispatching new events
@@ -75,18 +93,18 @@
}
/**
- * Schedule a series of events for injection. These events must comprise a complete, valid
- * sequence. All gestures currently in progress will be cancelled, and all {@code downTime}
- * and {@code eventTime} fields will be offset by the current time.
+ * Schedule a gesture for injection. The gesture is defined by a set of {@code GestureStep}s,
+ * from which {@code MotionEvent}s will be derived. All gestures currently in progress will be
+ * cancelled.
*
- * @param events The events to inject. Must all be from the same source.
+ * @param gestureSteps The gesture steps to inject.
* @param serviceInterface The interface to call back with a result when the gesture is
* either complete or cancelled.
*/
- public void injectEvents(List<MotionEvent> events,
+ public void injectEvents(List<GestureStep> gestureSteps,
IAccessibilityServiceClient serviceInterface, int sequence) {
SomeArgs args = SomeArgs.obtain();
- args.arg1 = events;
+ args.arg1 = gestureSteps;
args.arg2 = serviceInterface;
args.argi1 = sequence;
mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_INJECT_EVENTS, args));
@@ -138,7 +156,7 @@
public boolean handleMessage(Message message) {
if (message.what == MESSAGE_INJECT_EVENTS) {
SomeArgs args = (SomeArgs) message.obj;
- injectEventsMainThread((List<MotionEvent>) args.arg1,
+ injectEventsMainThread((List<GestureStep>) args.arg1,
(IAccessibilityServiceClient) args.arg2, args.argi1);
args.recycle();
return true;
@@ -148,16 +166,16 @@
return false;
}
MotionEvent motionEvent = (MotionEvent) message.obj;
- sendMotionEventToNext(motionEvent, motionEvent,
- WindowManagerPolicy.FLAG_PASS_TO_USER);
- // If the message queue is now empty, then this gesture is complete
- if (!mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
- notifyService(true);
+ sendMotionEventToNext(motionEvent, motionEvent, WindowManagerPolicy.FLAG_PASS_TO_USER);
+ boolean isEndOfSequence = message.arg1 != 0;
+ if (isEndOfSequence) {
+ notifyService(mServiceInterfaceForCurrentGesture, mSequencesInProgress.get(0), true);
+ mSequencesInProgress.remove(0);
}
return true;
}
- private void injectEventsMainThread(List<MotionEvent> events,
+ private void injectEventsMainThread(List<GestureStep> gestureSteps,
IAccessibilityServiceClient serviceInterface, int sequence) {
if (mIsDestroyed) {
try {
@@ -168,48 +186,110 @@
}
return;
}
- cancelAnyPendingInjectedEvents();
- mSourceOfInjectedGesture = events.get(0).getSource();
- cancelAnyGestureInProgress(mSourceOfInjectedGesture);
- mServiceInterfaceForCurrentGesture = serviceInterface;
- mSequenceForCurrentGesture = sequence;
+
if (mNext == null) {
- notifyService(false);
+ notifyService(serviceInterface, sequence, false);
return;
}
- long startTime = SystemClock.uptimeMillis();
+ boolean continuingGesture = newGestureTriesToContinueOldOne(gestureSteps);
+
+ if (continuingGesture) {
+ if ((serviceInterface != mServiceInterfaceForCurrentGesture)
+ || !prepareToContinueOldGesture(gestureSteps)) {
+ cancelAnyPendingInjectedEvents();
+ notifyService(serviceInterface, sequence, false);
+ return;
+ }
+ }
+ if (!continuingGesture) {
+ cancelAnyPendingInjectedEvents();
+ // Injected gestures have been canceled, but real gestures still need cancelling
+ cancelAnyGestureInProgress(EVENT_SOURCE);
+ }
+ mServiceInterfaceForCurrentGesture = serviceInterface;
+
+ long currentTime = SystemClock.uptimeMillis();
+ List<MotionEvent> events = getMotionEventsFromGestureSteps(gestureSteps,
+ (mSequencesInProgress.size() == 0) ? currentTime : mLastScheduledEventTime);
+ if (events.isEmpty()) {
+ notifyService(serviceInterface, sequence, false);
+ return;
+ }
+ mSequencesInProgress.add(sequence);
+
for (int i = 0; i < events.size(); i++) {
MotionEvent event = events.get(i);
- int numPointers = event.getPointerCount();
- if (numPointers > mPointerCoords.length) {
- mPointerCoords = new MotionEvent.PointerCoords[numPointers];
- mPointerProperties = new MotionEvent.PointerProperties[numPointers];
- }
- for (int j = 0; j < numPointers; j++) {
- if (mPointerCoords[j] == null) {
- mPointerCoords[j] = new MotionEvent.PointerCoords();
- mPointerProperties[j] = new MotionEvent.PointerProperties();
- }
- event.getPointerCoords(j, mPointerCoords[j]);
- event.getPointerProperties(j, mPointerProperties[j]);
- }
-
- /*
- * MotionEvent doesn't have a setEventTime() method (it carries around history data,
- * which could become inconsistent), so we need to obtain a new one.
- */
- MotionEvent offsetEvent = MotionEvent.obtain(startTime + event.getDownTime(),
- startTime + event.getEventTime(), event.getAction(), numPointers,
- mPointerProperties, mPointerCoords, event.getMetaState(),
- event.getButtonState(), event.getXPrecision(), event.getYPrecision(),
- event.getDeviceId(), event.getEdgeFlags(), event.getSource(),
- event.getFlags());
- Message message = mHandler.obtainMessage(MESSAGE_SEND_MOTION_EVENT, offsetEvent);
- mHandler.sendMessageDelayed(message, event.getEventTime());
+ int isEndOfSequence = (i == events.size() - 1) ? 1 : 0;
+ Message message = mHandler.obtainMessage(
+ MESSAGE_SEND_MOTION_EVENT, isEndOfSequence, 0, event);
+ mLastScheduledEventTime = event.getEventTime();
+ mHandler.sendMessageDelayed(message, Math.max(0, event.getEventTime() - currentTime));
}
}
+ private boolean newGestureTriesToContinueOldOne(List<GestureStep> gestureSteps) {
+ if (gestureSteps.isEmpty()) {
+ return false;
+ }
+ GestureStep firstStep = gestureSteps.get(0);
+ for (int i = 0; i < firstStep.numTouchPoints; i++) {
+ if (!firstStep.touchPoints[i].mIsStartOfPath) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * A gesture can only continue a gesture if it contains intermediate points that continue
+ * each continued stroke of the last gesture, and no extra points.
+ *
+ * @param gestureSteps The steps of the new gesture
+ * @return {@code true} if the new gesture could continue the last one dispatched. {@code false}
+ * otherwise.
+ */
+ private boolean prepareToContinueOldGesture(List<GestureStep> gestureSteps) {
+ if (gestureSteps.isEmpty() || (mLastTouchPoints == null) || (mNumLastTouchPoints == 0)) {
+ return false;
+ }
+ GestureStep firstStep = gestureSteps.get(0);
+ // Make sure all of the continuing paths match up
+ int numContinuedStrokes = 0;
+ for (int i = 0; i < firstStep.numTouchPoints; i++) {
+ TouchPoint touchPoint = firstStep.touchPoints[i];
+ if (!touchPoint.mIsStartOfPath) {
+ int continuedPointerId = mStrokeIdToPointerId
+ .get(touchPoint.mContinuedStrokeId, -1);
+ if (continuedPointerId == -1) {
+ return false;
+ }
+ mStrokeIdToPointerId.put(touchPoint.mStrokeId, continuedPointerId);
+ int lastPointIndex = findPointByStrokeId(
+ mLastTouchPoints, mNumLastTouchPoints, touchPoint.mContinuedStrokeId);
+ if (lastPointIndex < 0) {
+ return false;
+ }
+ if (mLastTouchPoints[lastPointIndex].mIsEndOfPath
+ || (mLastTouchPoints[lastPointIndex].mX != touchPoint.mX)
+ || (mLastTouchPoints[lastPointIndex].mY != touchPoint.mY)) {
+ return false;
+ }
+ // Update the last touch point to match the continuation, so the gestures will
+ // line up
+ mLastTouchPoints[lastPointIndex].mStrokeId = touchPoint.mStrokeId;
+ }
+ numContinuedStrokes++;
+ }
+ // Make sure we didn't miss any paths
+ for (int i = 0; i < mNumLastTouchPoints; i++) {
+ if (!mLastTouchPoints[i].mIsEndOfPath) {
+ numContinuedStrokes--;
+ }
+ }
+ return numContinuedStrokes == 0;
+ }
+
private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent,
int policyFlags) {
if (mNext != null) {
@@ -228,7 +308,7 @@
if ((mNext != null) && mOpenGesturesInProgress.get(source, false)) {
long now = SystemClock.uptimeMillis();
MotionEvent cancelEvent =
- MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+ obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
sendMotionEventToNext(cancelEvent, cancelEvent,
WindowManagerPolicy.FLAG_PASS_TO_USER);
mOpenGesturesInProgress.put(source, false);
@@ -237,19 +317,187 @@
private void cancelAnyPendingInjectedEvents() {
if (mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
- cancelAnyGestureInProgress(mSourceOfInjectedGesture);
mHandler.removeMessages(MESSAGE_SEND_MOTION_EVENT);
- notifyService(false);
+ cancelAnyGestureInProgress(EVENT_SOURCE);
+ for (int i = mSequencesInProgress.size() - 1; i >= 0; i--) {
+ notifyService(mServiceInterfaceForCurrentGesture,
+ mSequencesInProgress.get(i), false);
+ mSequencesInProgress.remove(i);
+ }
+ } else if (mNumLastTouchPoints != 0) {
+ // An injected gesture is in progress and waiting for a continuation. Cancel it.
+ cancelAnyGestureInProgress(EVENT_SOURCE);
}
+ mNumLastTouchPoints = 0;
+ mStrokeIdToPointerId.clear();
}
- private void notifyService(boolean success) {
+ private void notifyService(IAccessibilityServiceClient service, int sequence, boolean success) {
try {
- mServiceInterfaceForCurrentGesture.onPerformGestureResult(
- mSequenceForCurrentGesture, success);
+ service.onPerformGestureResult(sequence, success);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error sending motion event injection status to "
+ mServiceInterfaceForCurrentGesture, re);
}
}
+
+ private List<MotionEvent> getMotionEventsFromGestureSteps(
+ List<GestureStep> steps, long startTime) {
+ final List<MotionEvent> motionEvents = new ArrayList<>();
+
+ TouchPoint[] lastTouchPoints = getLastTouchPoints();
+
+ for (int i = 0; i < steps.size(); i++) {
+ GestureDescription.GestureStep step = steps.get(i);
+ int currentTouchPointSize = step.numTouchPoints;
+ if (currentTouchPointSize > lastTouchPoints.length) {
+ mNumLastTouchPoints = 0;
+ motionEvents.clear();
+ return motionEvents;
+ }
+
+ appendMoveEventIfNeeded(motionEvents, step.touchPoints, currentTouchPointSize,
+ startTime + step.timeSinceGestureStart);
+ appendUpEvents(motionEvents, step.touchPoints, currentTouchPointSize,
+ startTime + step.timeSinceGestureStart);
+ appendDownEvents(motionEvents, step.touchPoints, currentTouchPointSize,
+ startTime + step.timeSinceGestureStart);
+ }
+ return motionEvents;
+ }
+
+ private TouchPoint[] getLastTouchPoints() {
+ if (mLastTouchPoints == null) {
+ int capacity = GestureDescription.getMaxStrokeCount();
+ mLastTouchPoints = new TouchPoint[capacity];
+ for (int i = 0; i < capacity; i++) {
+ mLastTouchPoints[i] = new GestureDescription.TouchPoint();
+ }
+ }
+ return mLastTouchPoints;
+ }
+
+ private void appendMoveEventIfNeeded(List<MotionEvent> motionEvents,
+ TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
+ /* Look for pointers that have moved */
+ boolean moveFound = false;
+ TouchPoint[] lastTouchPoints = getLastTouchPoints();
+ for (int i = 0; i < currentTouchPointsSize; i++) {
+ int lastPointsIndex = findPointByStrokeId(lastTouchPoints, mNumLastTouchPoints,
+ currentTouchPoints[i].mStrokeId);
+ if (lastPointsIndex >= 0) {
+ moveFound |= (lastTouchPoints[lastPointsIndex].mX != currentTouchPoints[i].mX)
+ || (lastTouchPoints[lastPointsIndex].mY != currentTouchPoints[i].mY);
+ lastTouchPoints[lastPointsIndex].copyFrom(currentTouchPoints[i]);
+ }
+ }
+
+ if (moveFound) {
+ motionEvents.add(obtainMotionEvent(mDownTime, currentTime, MotionEvent.ACTION_MOVE,
+ lastTouchPoints, mNumLastTouchPoints));
+ }
+ }
+
+ private void appendUpEvents(List<MotionEvent> motionEvents,
+ TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
+ /* Look for a pointer at the end of its path */
+ TouchPoint[] lastTouchPoints = getLastTouchPoints();
+ for (int i = 0; i < currentTouchPointsSize; i++) {
+ if (currentTouchPoints[i].mIsEndOfPath) {
+ int indexOfUpEvent = findPointByStrokeId(lastTouchPoints, mNumLastTouchPoints,
+ currentTouchPoints[i].mStrokeId);
+ if (indexOfUpEvent < 0) {
+ continue; // Should not happen
+ }
+ int action = (mNumLastTouchPoints == 1) ? MotionEvent.ACTION_UP
+ : MotionEvent.ACTION_POINTER_UP;
+ action |= indexOfUpEvent << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ motionEvents.add(obtainMotionEvent(mDownTime, currentTime, action,
+ lastTouchPoints, mNumLastTouchPoints));
+ /* Remove this point from lastTouchPoints */
+ for (int j = indexOfUpEvent; j < mNumLastTouchPoints - 1; j++) {
+ lastTouchPoints[j].copyFrom(mLastTouchPoints[j + 1]);
+ }
+ mNumLastTouchPoints--;
+ if (mNumLastTouchPoints == 0) {
+ mStrokeIdToPointerId.clear();
+ }
+ }
+ }
+ }
+
+ private void appendDownEvents(List<MotionEvent> motionEvents,
+ TouchPoint[] currentTouchPoints, int currentTouchPointsSize, long currentTime) {
+ /* Look for a pointer that is just starting */
+ TouchPoint[] lastTouchPoints = getLastTouchPoints();
+ for (int i = 0; i < currentTouchPointsSize; i++) {
+ if (currentTouchPoints[i].mIsStartOfPath) {
+ /* Add the point to last coords and use the new array to generate the event */
+ lastTouchPoints[mNumLastTouchPoints++].copyFrom(currentTouchPoints[i]);
+ int action = (mNumLastTouchPoints == 1) ? MotionEvent.ACTION_DOWN
+ : MotionEvent.ACTION_POINTER_DOWN;
+ if (action == MotionEvent.ACTION_DOWN) {
+ mDownTime = currentTime;
+ }
+ action |= i << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ motionEvents.add(obtainMotionEvent(mDownTime, currentTime, action,
+ lastTouchPoints, mNumLastTouchPoints));
+ }
+ }
+ }
+
+ private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action,
+ TouchPoint[] touchPoints, int touchPointsSize) {
+ if ((sPointerCoords == null) || (sPointerCoords.length < touchPointsSize)) {
+ sPointerCoords = new MotionEvent.PointerCoords[touchPointsSize];
+ for (int i = 0; i < touchPointsSize; i++) {
+ sPointerCoords[i] = new MotionEvent.PointerCoords();
+ }
+ }
+ if ((sPointerProps == null) || (sPointerProps.length < touchPointsSize)) {
+ sPointerProps = new MotionEvent.PointerProperties[touchPointsSize];
+ for (int i = 0; i < touchPointsSize; i++) {
+ sPointerProps[i] = new MotionEvent.PointerProperties();
+ }
+ }
+ for (int i = 0; i < touchPointsSize; i++) {
+ int pointerId = mStrokeIdToPointerId.get(touchPoints[i].mStrokeId, -1);
+ if (pointerId == -1) {
+ pointerId = getUnusedPointerId();
+ mStrokeIdToPointerId.put(touchPoints[i].mStrokeId, pointerId);
+ }
+ sPointerProps[i].id = pointerId;
+ sPointerProps[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+ sPointerCoords[i].clear();
+ sPointerCoords[i].pressure = 1.0f;
+ sPointerCoords[i].size = 1.0f;
+ sPointerCoords[i].x = touchPoints[i].mX;
+ sPointerCoords[i].y = touchPoints[i].mY;
+ }
+ return MotionEvent.obtain(downTime, eventTime, action, touchPointsSize,
+ sPointerProps, sPointerCoords, EVENT_META_STATE, EVENT_BUTTON_STATE,
+ EVENT_X_PRECISION, EVENT_Y_PRECISION, EVENT_DEVICE_ID, EVENT_EDGE_FLAGS,
+ EVENT_SOURCE, EVENT_FLAGS);
+ }
+
+ private static int findPointByStrokeId(TouchPoint[] touchPoints, int touchPointsSize,
+ int strokeId) {
+ for (int i = 0; i < touchPointsSize; i++) {
+ if (touchPoints[i].mStrokeId == strokeId) {
+ return i;
+ }
+ }
+ return -1;
+ }
+ private int getUnusedPointerId() {
+ int MAX_POINTER_ID = 10;
+ int pointerId = 0;
+ while (mStrokeIdToPointerId.indexOfValue(pointerId) >= 0) {
+ pointerId++;
+ if (pointerId >= MAX_POINTER_ID) {
+ return MAX_POINTER_ID;
+ }
+ }
+ return pointerId;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8151c8a..31ecb75 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2399,16 +2399,15 @@
} catch (InterruptedException e) {
// just bail
Slog.w(TAG, "Interrupted: " + e);
- mActivityManager.clearPendingBackup();
- return null;
+ mConnecting = false;
+ mConnectedAgent = null;
}
}
// if we timed out with no connect, abort and move on
if (mConnecting == true) {
Slog.w(TAG, "Timeout waiting for agent " + app);
- mActivityManager.clearPendingBackup();
- return null;
+ mConnectedAgent = null;
}
if (DEBUG) Slog.i(TAG, "got agent " + mConnectedAgent);
agent = mConnectedAgent;
@@ -2417,6 +2416,13 @@
// can't happen - ActivityManager is local
}
}
+ if (agent == null) {
+ try {
+ mActivityManager.clearPendingBackup();
+ } catch (RemoteException e) {
+ // can't happen - ActivityManager is local
+ }
+ }
return agent;
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index f7068cf..b0c5603 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -42,6 +42,7 @@
import android.database.ContentObserver;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -52,6 +53,8 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.os.UserManagerInternal.UserRestrictionsListener;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Slog;
@@ -177,6 +180,24 @@
}
};
+ private final UserRestrictionsListener mUserRestrictionsListener =
+ new UserRestrictionsListener() {
+ @Override
+ public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
+ Bundle prevRestrictions) {
+ final boolean bluetoothDisallowed =
+ newRestrictions.getBoolean(UserManager.DISALLOW_BLUETOOTH);
+ if ((mEnable || mEnableExternal) && bluetoothDisallowed) {
+ try {
+ disable(null, true);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception when disabling Bluetooth from UserRestrictionsListener",
+ e);
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -634,6 +655,13 @@
public boolean enableNoAutoConnect()
{
+ if (isBluetoothDisallowed()) {
+ if (DBG) {
+ Slog.d(TAG, "enableNoAutoConnect(): not enabling - bluetooth disallowed");
+ }
+ return false;
+ }
+
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH ADMIN permission");
@@ -659,6 +687,13 @@
final int callingUid = Binder.getCallingUid();
final boolean callerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
+ if (isBluetoothDisallowed()) {
+ if (DBG) {
+ Slog.d(TAG,"enable(): not enabling - bluetooth disallowed");
+ }
+ return false;
+ }
+
if (!callerSystem) {
if (!checkIfCallerIsForegroundUser()) {
Slog.w(TAG, "enable(): not allowed for non-active and non system user");
@@ -872,6 +907,12 @@
*/
public void handleOnBootPhase() {
if (DBG) Slog.d(TAG, "Bluetooth boot completed");
+ UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ userManagerInternal.addUserRestrictionsListener(mUserRestrictionsListener);
+ if (isBluetoothDisallowed()) {
+ return;
+ }
if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
if (DBG) Slog.d(TAG, "Auto-enabling Bluetooth.");
sendEnableMsg(mQuietEnableExternal);
@@ -1916,6 +1957,16 @@
}
}
+ private boolean isBluetoothDisallowed() {
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return mContext.getSystemService(UserManager.class)
+ .hasUserRestriction(UserManager.DISALLOW_BLUETOOTH, UserHandle.SYSTEM);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index b2adc73..d94d3cd 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1830,11 +1830,10 @@
// If keyguard is showing, nothing is visible, except if we are able to dismiss Keyguard
// right away.
return shouldBeVisible && mStackSupervisor.mKeyguardController
- .canShowActivityWhileKeyguardShowing(dismissKeyguard);
+ .canShowActivityWhileKeyguardShowing(r, dismissKeyguard);
} else if (keyguardLocked) {
-
- // Show when locked windows above keyguard.
- return shouldBeVisible && showWhenLocked;
+ return shouldBeVisible && mStackSupervisor.mKeyguardController.canShowWhileOccluded(
+ dismissKeyguard, showWhenLocked);
} else {
return shouldBeVisible;
}
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index 5e02597..19bf536 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -57,6 +57,7 @@
private boolean mKeyguardShowing;
private boolean mKeyguardGoingAway;
private boolean mOccluded;
+ private boolean mDismissalRequested;
private ActivityRecord mDismissingKeyguardActivity;
private int mBeforeUnoccludeTransit;
private int mVisibilityTransactionDepth;
@@ -95,9 +96,7 @@
mKeyguardShowing = showing;
if (showing) {
mKeyguardGoingAway = false;
-
- // Allow an activity to redismiss Keyguard.
- mDismissingKeyguardActivity = null;
+ mDismissalRequested = false;
}
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
mService.updateSleepIfNeededLocked();
@@ -183,8 +182,20 @@
* @return True if we may show an activity while Keyguard is showing because we are in the
* process of dismissing it anyways, false otherwise.
*/
- boolean canShowActivityWhileKeyguardShowing(boolean dismissKeyguard) {
- return dismissKeyguard && canDismissKeyguard();
+ boolean canShowActivityWhileKeyguardShowing(ActivityRecord r, boolean dismissKeyguard) {
+
+ // Allow to show it when we are about to dismiss Keyguard. This isn't allowed if r is
+ // already the dismissing activity, in which case we don't allow it to repeatedly dismiss
+ // Keyguard.
+ return dismissKeyguard && canDismissKeyguard() &&
+ (mDismissalRequested || r != mDismissingKeyguardActivity);
+ }
+
+ /**
+ * @return True if we may show an activity while Keyguard is occluded, false otherwise.
+ */
+ boolean canShowWhileOccluded(boolean dismissKeyguard, boolean showWhenLocked) {
+ return showWhenLocked || dismissKeyguard && !mWindowManager.isKeyguardSecure();
}
private void visibilitiesUpdated() {
@@ -199,7 +210,14 @@
// Only the very top activity may control occluded state
if (stackNdx == topStackNdx) {
- mOccluded = stack.topActivityOccludesKeyguard();
+
+ // A dismissing activity occludes Keyguard in the insecure case for legacy reasons.
+ final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
+ mOccluded = stack.topActivityOccludesKeyguard()
+ || (topDismissing != null
+ && stack.topRunningActivityLocked() == topDismissing
+ && canShowWhileOccluded(true /* dismissKeyguard */,
+ false /* showWhenLocked */));
}
if (mDismissingKeyguardActivity == null
&& stack.getTopDismissingKeyguardActivity() != null) {
@@ -239,8 +257,13 @@
* Called when somebody might want to dismiss the Keyguard.
*/
private void handleDismissKeyguard() {
- if (mDismissingKeyguardActivity != null) {
+ // We only allow dismissing Keyguard via the flag when Keyguard is secure for legacy
+ // reasons, because that's how apps used to dismiss Keyguard in the secure case. In the
+ // insecure case, we actually show it on top of the lockscreen. See #canShowWhileOccluded.
+ if (!mOccluded && mDismissingKeyguardActivity != null
+ && mWindowManager.isKeyguardSecure()) {
mWindowManager.dismissKeyguard(null /* callback */);
+ mDismissalRequested = true;
// If we are about to unocclude the Keyguard, but we can dismiss it without security,
// we immediately dismiss the Keyguard so the activity gets shown without a flicker.
@@ -296,6 +319,7 @@
pw.println(prefix + " mKeyguardGoingAway=" + mKeyguardGoingAway);
pw.println(prefix + " mOccluded=" + mOccluded);
pw.println(prefix + " mDismissingKeyguardActivity=" + mDismissingKeyguardActivity);
+ pw.println(prefix + " mDismissalRequested=" + mDismissalRequested);
pw.println(prefix + " mVisibilityTransactionDepth=" + mVisibilityTransactionDepth);
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index aa66917..5e98859 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -97,6 +97,24 @@
private static final int SOCKET_TIMEOUT_MS = 10000;
private static final int PROBE_TIMEOUT_MS = 3000;
+ static enum EvaluationResult {
+ VALIDATED(true),
+ CAPTIVE_PORTAL(false);
+ final boolean isValidated;
+ EvaluationResult(boolean isValidated) {
+ this.isValidated = isValidated;
+ }
+ }
+
+ static enum ValidationStage {
+ FIRST_VALIDATION(true),
+ REVALIDATION(false);
+ final boolean isFirstValidation;
+ ValidationStage(boolean isFirstValidation) {
+ this.isFirstValidation = isFirstValidation;
+ }
+ }
+
public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
"android.net.conn.NETWORK_CONDITIONS_MEASURED";
public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
@@ -216,6 +234,8 @@
protected boolean mIsCaptivePortalCheckEnabled;
private boolean mUseHttps;
+ // The total number of captive portal detection attempts for this NetworkMonitor instance.
+ private int mValidations = 0;
// Set if the user explicitly selected "Do not use this network" in captive portal sign-in app.
private boolean mUserDoesNotWant = false;
@@ -290,6 +310,10 @@
return validationLogs.readOnlyLocalLog();
}
+ private ValidationStage validationStage() {
+ return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION;
+ }
+
// DefaultState is the parent of all States. It exists only to handle CMD_* messages but
// does not entail any real state (hence no enter() or exit() routines).
private class DefaultState extends State {
@@ -366,9 +390,11 @@
private class ValidatedState extends State {
@Override
public void enter() {
- maybeLogEvaluationResult(NetworkEvent.NETWORK_VALIDATED);
+ maybeLogEvaluationResult(
+ networkEventType(validationStage(), EvaluationResult.VALIDATED));
mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null));
+ mValidations++;
}
@Override
@@ -584,7 +610,8 @@
@Override
public void enter() {
- maybeLogEvaluationResult(NetworkEvent.NETWORK_CAPTIVE_PORTAL_FOUND);
+ maybeLogEvaluationResult(
+ networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL));
// Don't annoy user with sign-in notifications.
if (mDontDisplaySigninNotification) return;
// Create a CustomIntentReceiver that sends us a
@@ -604,6 +631,7 @@
// Retest for captive portal occasionally.
sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
+ mValidations++;
}
@Override
@@ -679,48 +707,13 @@
long startTime = SystemClock.elapsedRealtime();
- // Pre-resolve the captive portal server host so we can log it.
- // Only do this if HttpURLConnection is about to, to avoid any potentially
- // unnecessary resolution.
- String hostToResolve = null;
+ final CaptivePortalProbeResult result;
if (pacUrl != null) {
- hostToResolve = pacUrl.getHost();
- } else if (proxyInfo != null) {
- hostToResolve = proxyInfo.getHost();
- } else {
- hostToResolve = httpUrl.getHost();
- }
-
- if (!TextUtils.isEmpty(hostToResolve)) {
- String probeName = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
- final Stopwatch dnsTimer = new Stopwatch().start();
- int dnsResult;
- long dnsLatency;
- try {
- InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(hostToResolve);
- dnsResult = ValidationProbeEvent.DNS_SUCCESS;
- dnsLatency = dnsTimer.stop();
- final StringBuffer connectInfo = new StringBuffer(", " + hostToResolve + "=");
- for (InetAddress address : addresses) {
- connectInfo.append(address.getHostAddress());
- if (address != addresses[addresses.length-1]) connectInfo.append(",");
- }
- validationLog(probeName + " OK " + dnsLatency + "ms" + connectInfo);
- } catch (UnknownHostException e) {
- dnsResult = ValidationProbeEvent.DNS_FAILURE;
- dnsLatency = dnsTimer.stop();
- validationLog(probeName + " FAIL " + dnsLatency + "ms, " + hostToResolve);
- }
- logValidationProbe(dnsLatency, ValidationProbeEvent.PROBE_DNS, dnsResult);
- }
-
- CaptivePortalProbeResult result;
- if (pacUrl != null) {
- result = sendHttpProbe(pacUrl, ValidationProbeEvent.PROBE_PAC);
+ result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
} else if (mUseHttps) {
- result = sendParallelHttpProbes(httpsUrl, httpUrl, fallbackUrl);
+ result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl, fallbackUrl);
} else {
- result = sendHttpProbe(httpUrl, ValidationProbeEvent.PROBE_HTTP);
+ result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
}
long endTime = SystemClock.elapsedRealtime();
@@ -733,8 +726,50 @@
}
/**
- * Do a URL fetch on a known server to see if we get the data we expect.
- * Returns HTTP response code.
+ * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect.
+ * @return a CaptivePortalProbeResult inferred from the HTTP response.
+ */
+ private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) {
+ // Pre-resolve the captive portal server host so we can log it.
+ // Only do this if HttpURLConnection is about to, to avoid any potentially
+ // unnecessary resolution.
+ final String host = (proxy != null) ? proxy.getHost() : url.getHost();
+ sendDnsProbe(host);
+ return sendHttpProbe(url, probeType);
+ }
+
+ /** Do a DNS resolution of the given server. */
+ private void sendDnsProbe(String host) {
+ if (TextUtils.isEmpty(host)) {
+ return;
+ }
+
+ final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS);
+ final Stopwatch watch = new Stopwatch().start();
+ int result;
+ String connectInfo;
+ try {
+ InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(host);
+ result = ValidationProbeEvent.DNS_SUCCESS;
+ StringBuffer buffer = new StringBuffer(host).append("=");
+ for (InetAddress address : addresses) {
+ buffer.append(address.getHostAddress());
+ if (address != addresses[addresses.length-1]) buffer.append(",");
+ }
+ connectInfo = buffer.toString();
+ } catch (UnknownHostException e) {
+ result = ValidationProbeEvent.DNS_FAILURE;
+ connectInfo = host;
+ }
+ final long latency = watch.stop();
+ String resultString = (ValidationProbeEvent.DNS_SUCCESS == result) ? "OK" : "FAIL";
+ validationLog(String.format("%s %s %dms, %s", name, resultString, latency, connectInfo));
+ logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result);
+ }
+
+ /**
+ * Do a URL fetch on a known web server to see if we get the data we expect.
+ * @return a CaptivePortalProbeResult inferred from the HTTP response.
*/
@VisibleForTesting
protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType) {
@@ -801,7 +836,7 @@
}
private CaptivePortalProbeResult sendParallelHttpProbes(
- URL httpsUrl, URL httpUrl, URL fallbackUrl) {
+ ProxyInfo proxy, URL httpsUrl, URL httpUrl, URL fallbackUrl) {
// Number of probes to wait for. If a probe completes with a conclusive answer
// it shortcuts the latch immediately by forcing the count to 0.
final CountDownLatch latch = new CountDownLatch(2);
@@ -821,9 +856,10 @@
@Override
public void run() {
if (mIsHttps) {
- mResult = sendHttpProbe(httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
+ mResult =
+ sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS);
} else {
- mResult = sendHttpProbe(httpUrl, ValidationProbeEvent.PROBE_HTTP);
+ mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP);
}
if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) {
// Stop waiting immediately if https succeeds or if http finds a portal.
@@ -974,6 +1010,22 @@
mMetricsLog.log(new NetworkEvent(mNetId, evtype));
}
+ private int networkEventType(ValidationStage s, EvaluationResult r) {
+ if (s.isFirstValidation) {
+ if (r.isValidated) {
+ return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS;
+ } else {
+ return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND;
+ }
+ } else {
+ if (r.isValidated) {
+ return NetworkEvent.NETWORK_REVALIDATION_SUCCESS;
+ } else {
+ return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND;
+ }
+ }
+ }
+
private void maybeLogEvaluationResult(int evtype) {
if (mEvaluationTimer.isRunning()) {
mMetricsLog.log(new NetworkEvent(mNetId, evtype, mEvaluationTimer.stop()));
@@ -982,6 +1034,8 @@
}
private void logValidationProbe(long durationMs, int probeType, int probeResult) {
+ probeType =
+ ValidationProbeEvent.makeProbeType(probeType, validationStage().isFirstValidation);
mMetricsLog.log(new ValidationProbeEvent(mNetId, durationMs, probeType, probeResult));
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dc25ce4..dd410e2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9086,6 +9086,13 @@
PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
final String curPackageName = cur == null ? null : cur.info.packageName;
+ // Dont allow ephemeral apps to define new permission groups.
+ if (pkg.applicationInfo.isEphemeralApp()) {
+ Slog.w(TAG, "Permission group " + pg.info.name + " from package "
+ + pg.info.packageName
+ + " ignored: ephemeral apps cannot define new permission groups.");
+ continue;
+ }
final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
if (cur == null || isPackageUpdate) {
mPermissionGroups.put(pg.info.name, pg);
@@ -9124,6 +9131,14 @@
for (i=0; i<N; i++) {
PackageParser.Permission p = pkg.permissions.get(i);
+ // Dont allow ephemeral apps to define new permissions.
+ if (pkg.applicationInfo.isEphemeralApp()) {
+ Slog.w(TAG, "Permission " + p.info.name + " from package "
+ + p.info.packageName
+ + " ignored: ephemeral apps cannot define new permissions.");
+ continue;
+ }
+
// Assume by default that we did not install this permission into the system.
p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index 4de15de..e5a2f5a 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -23,7 +23,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.backup.BackupUtils;
-import libcore.io.Base64;
import libcore.util.HexEncoding;
import org.xmlpull.v1.XmlPullParser;
@@ -33,6 +32,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Base64;
/**
* Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
@@ -161,7 +161,8 @@
for (int i = 0; i < mSigHashes.size(); i++) {
out.startTag(null, TAG_SIGNATURE);
- ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, Base64.encode(mSigHashes.get(i)));
+ final String encoded = Base64.getEncoder().encodeToString(mSigHashes.get(i));
+ ShortcutService.writeAttr(out, ATTR_SIGNATURE_HASH, encoded);
out.endTag(null, TAG_SIGNATURE);
}
out.endTag(null, TAG_ROOT);
@@ -196,7 +197,9 @@
case TAG_SIGNATURE: {
final String hash = ShortcutService.parseStringAttribute(
parser, ATTR_SIGNATURE_HASH);
- hashes.add(Base64.decode(hash.getBytes()));
+ // Throws IllegalArgumentException if hash is invalid base64 data
+ final byte[] decoded = Base64.getDecoder().decode(hash);
+ hashes.add(decoded);
continue;
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c4241e7..bac7a76 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -913,17 +913,36 @@
@Override
public boolean isUserUnlockingOrUnlocked(int userId) {
- int callingUserId = UserHandle.getCallingUserId();
- if (callingUserId != userId && !hasManageUsersPermission()) {
- if (!isSameProfileGroupNoChecks(callingUserId, userId)) {
- throw new SecurityException(
- "You need MANAGE_USERS permission to: check isUserUnlockingOrUnlocked");
- }
- }
+ checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlockingOrUnlocked");
return mLocalService.isUserUnlockingOrUnlocked(userId);
}
@Override
+ public boolean isUserUnlocked(int userId) {
+ checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlocked");
+ return mLocalService.isUserUnlockingOrUnlocked(userId);
+ }
+
+ @Override
+ public boolean isUserRunning(int userId) {
+ checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserRunning");
+ return mLocalService.isUserRunning(userId);
+ }
+
+ private void checkManageOrInteractPermIfCallerInOtherProfileGroup(int userId, String name) {
+ int callingUserId = UserHandle.getCallingUserId();
+ if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
+ hasManageUsersPermission()) {
+ return;
+ }
+ if (ActivityManager.checkComponentPermission(Manifest.permission.INTERACT_ACROSS_USERS,
+ Binder.getCallingUid(), -1, true) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need INTERACT_ACROSS_USERS or MANAGE_USERS permission "
+ + "to: check " + name);
+ }
+ }
+
+ @Override
public boolean isDemoUser(int userId) {
int callingUserId = UserHandle.getCallingUserId();
if (callingUserId != userId && !hasManageUsersPermission()) {
@@ -3641,6 +3660,14 @@
|| (state == UserState.STATE_RUNNING_UNLOCKED);
}
}
+
+ @Override
+ public boolean isUserUnlocked(int userId) {
+ synchronized (mUserStates) {
+ int state = mUserStates.get(userId, -1);
+ return state == UserState.STATE_RUNNING_UNLOCKED;
+ }
+ }
}
/* Remove all the users except of the system one. */
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 9fe0922..7ec3c19 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -71,6 +71,7 @@
UserManager.DISALLOW_SHARE_LOCATION,
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
UserManager.DISALLOW_CONFIG_BLUETOOTH,
+ UserManager.DISALLOW_BLUETOOTH,
UserManager.DISALLOW_USB_FILE_TRANSFER,
UserManager.DISALLOW_CONFIG_CREDENTIALS,
UserManager.DISALLOW_REMOVE_USER,
@@ -117,6 +118,7 @@
* User restrictions that can not be set by profile owners.
*/
private static final Set<String> DEVICE_OWNER_ONLY_RESTRICTIONS = Sets.newArraySet(
+ UserManager.DISALLOW_BLUETOOTH,
UserManager.DISALLOW_USB_FILE_TRANSFER,
UserManager.DISALLOW_CONFIG_TETHERING,
UserManager.DISALLOW_NETWORK_RESET,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e73acde..79d58a3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -141,8 +141,6 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -459,6 +457,43 @@
}
@Override
+ boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
+ // Special handling so we can process IME windows with #forAllImeWindows above their IME
+ // target, or here in order if there isn't an IME target.
+ if (traverseTopToBottom) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final DisplayChildWindowContainer child = mChildren.get(i);
+ if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
+ // In this case the Ime windows will be processed above their target so we skip
+ // here.
+ continue;
+ }
+ if (child.forAllWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ }
+ } else {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ final DisplayChildWindowContainer child = mChildren.get(i);
+ if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
+ // In this case the Ime windows will be processed above their target so we skip
+ // here.
+ continue;
+ }
+ if (child.forAllWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean forAllImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
+ return mImeWindowsContainers.forAllWindows(callback, traverseTopToBottom);
+ }
+
+ @Override
int getOrientation() {
final WindowManagerPolicy policy = mService.mPolicy;
@@ -2546,7 +2581,7 @@
if (!obscured) {
final boolean isDisplayed = w.isDisplayedLw();
- if (isDisplayed && w.isObscuringFullscreen(mDisplayInfo)) {
+ if (isDisplayed && w.isObscuringDisplay()) {
// This window completely covers everything behind it, so we want to leave all
// of them as undimmed (for performance reasons).
root.mObscuringWindow = w;
@@ -2945,7 +2980,7 @@
screenshotReady = true;
}
- if (ws.isObscuringFullscreen(mDisplayInfo)){
+ if (ws.isObscuringDisplay()){
break;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index 32373f9..c2988ad 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -66,6 +66,8 @@
private boolean mAnyLayerChanged;
private int mHighestLayerInImeTargetBaseLayer;
private WindowState mImeTarget;
+ private boolean mAboveImeTarget;
+ private ArrayDeque<WindowState> mAboveImeTargetAppWindows = new ArrayDeque();
final void assignWindowLayers(DisplayContent dc) {
if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based",
@@ -143,6 +145,8 @@
mImeTarget = mService.mInputMethodTarget;
mHighestLayerInImeTargetBaseLayer = (mImeTarget != null) ? mImeTarget.mBaseLayer : 0;
+ mAboveImeTarget = false;
+ mAboveImeTargetAppWindows.clear();
}
private void collectSpecialWindows(WindowState w) {
@@ -157,6 +161,20 @@
mInputMethodWindows.add(w);
return;
}
+ if (mImeTarget != null) {
+ if (w.getParentWindow() == mImeTarget && w.mSubLayer > 0) {
+ // Child windows of the ime target with a positive sub-layer should be placed above
+ // the IME.
+ mAboveImeTargetAppWindows.add(w);
+ } else if (mAboveImeTarget && w.mAppToken != null) {
+ // windows of apps above the IME target should be placed above the IME.
+ mAboveImeTargetAppWindows.add(w);
+ }
+ if (w == mImeTarget) {
+ mAboveImeTarget = true;
+ }
+ }
+
final Task task = w.getTask();
if (task == null) {
return;
@@ -211,6 +229,12 @@
while (!mInputMethodWindows.isEmpty()) {
layer = assignAndIncreaseLayerIfNeeded(mInputMethodWindows.remove(), layer);
}
+
+ // Adjust app windows the should be displayed above the IME since they are above the IME
+ // target.
+ while (!mAboveImeTargetAppWindows.isEmpty()) {
+ layer = assignAndIncreaseLayerIfNeeded(mAboveImeTargetAppWindows.remove(), layer);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 572581e..1b5e817 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -60,8 +60,6 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
-import java.util.function.Consumer;
-import java.util.function.Function;
import java.util.function.Predicate;
import static android.app.ActivityManager.StackId;
@@ -91,7 +89,6 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
@@ -1654,18 +1651,16 @@
&& (!mIsChildWindow || !getParentWindow().hasMoved());
}
- boolean isObscuringFullscreen(final DisplayInfo displayInfo) {
+ boolean isObscuringDisplay() {
Task task = getTask();
if (task != null && task.mStack != null && !task.mStack.fillsParent()) {
return false;
}
- if (!isOpaqueDrawn() || !isFrameFullscreen(displayInfo)) {
- return false;
- }
- return true;
+ return isOpaqueDrawn() && fillsDisplay();
}
- boolean isFrameFullscreen(final DisplayInfo displayInfo) {
+ boolean fillsDisplay() {
+ final DisplayInfo displayInfo = getDisplayInfo();
return mFrame.left <= 0 && mFrame.top <= 0
&& mFrame.right >= displayInfo.appWidth && mFrame.bottom >= displayInfo.appHeight;
}
@@ -3256,7 +3251,7 @@
pw.print(mPolicyVisibilityAfterAnim);
pw.print(" mAppOpVisibility=");
pw.print(mAppOpVisibility);
- pw.print(" parentHidden="); pw.println(isParentWindowHidden());
+ pw.print(" parentHidden="); pw.print(isParentWindowHidden());
pw.print(" mPermanentlyHidden="); pw.println(mPermanentlyHidden);
}
if (!mRelayoutCalled || mLayoutNeeded) {
@@ -3842,7 +3837,7 @@
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (mChildren.isEmpty()) {
// The window has no children so we just return it.
- return callback.apply(this);
+ return applyInOrderWithImeWindows(callback, traverseTopToBottom);
}
if (traverseTopToBottom) {
@@ -3871,7 +3866,7 @@
child = mChildren.get(i);
}
- if (callback.apply(this)) {
+ if (applyInOrderWithImeWindows(callback, false /* traverseTopToBottom */)) {
return true;
}
@@ -3907,7 +3902,7 @@
child = mChildren.get(i);
}
- if (callback.apply(this)) {
+ if (applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
@@ -3925,6 +3920,35 @@
return false;
}
+ private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
+ boolean traverseTopToBottom) {
+ if (traverseTopToBottom) {
+ if (mService.mInputMethodTarget == this) {
+ // This window is the current IME target, so we need to process the IME windows
+ // directly above it.
+ if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ }
+ if (callback.apply(this)) {
+ return true;
+ }
+ } else {
+ if (callback.apply(this)) {
+ return true;
+ }
+ if (mService.mInputMethodTarget == this) {
+ // This window is the current IME target, so we need to process the IME windows
+ // directly above it.
+ if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
WindowState getWindow(Predicate<WindowState> callback) {
if (callback.test(this)) {
return this;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 04db499..37bd402 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -57,7 +57,6 @@
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.MagnificationSpec;
-import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -1108,10 +1107,9 @@
/**
* Calculate the window-space crop rect and fill clipRect.
- * @return true if clipRect has been filled otherwise, no window space
- * crop should be applied.
+ * @return true if clipRect has been filled otherwise, no window space crop should be applied.
*/
- boolean calculateCrop(Rect clipRect) {
+ private boolean calculateCrop(Rect clipRect) {
final WindowState w = mWin;
final DisplayContent displayContent = w.getDisplayContent();
clipRect.setEmpty();
@@ -1130,7 +1128,6 @@
return false;
}
- final DisplayInfo displayInfo = displayContent.getDisplayInfo();
if (DEBUG_WINDOW_CROP) Slog.d(TAG,
"Updating crop win=" + w + " mLastCrop=" + mLastClipRect);
@@ -1139,7 +1136,7 @@
if (DEBUG_WINDOW_CROP) Slog.d(TAG, "Applying decor to crop win=" + w + " mDecorFrame="
+ w.mDecorFrame + " mSystemDecorRect=" + mSystemDecorRect);
- final boolean fullscreen = w.isFrameFullscreen(displayInfo);
+ final boolean fullscreen = w.fillsDisplay();
final boolean isFreeformResizing =
w.isDragResizing() && w.getResizeMode() == DRAG_RESIZE_MODE_FREEFORM;
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index a8356dc..83001df 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -194,8 +194,10 @@
{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
- private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
+ private static final int ICMP6_ROUTER_SOLICITATION = 133;
private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+ private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
+ private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
// NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
@@ -805,6 +807,8 @@
// if it's multicast and we're dropping multicast:
// drop
// pass
+ // if it's ICMPv6 RS to any:
+ // drop
// if it's ICMPv6 NA to ff02::1:
// drop
@@ -829,10 +833,12 @@
// Add unsolicited multicast neighbor announcements filter
String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
- // If not neighbor announcements, skip unsolicited multicast NA filter
gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
+ // Drop all router solicitations (b/32833400)
+ gen.addJumpIfR0Equals(ICMP6_ROUTER_SOLICITATION, gen.DROP_LABEL);
+ // If not neighbor announcements, skip filter.
gen.addJumpIfR0NotEquals(ICMP6_NEIGHBOR_ANNOUNCEMENT, skipUnsolicitedMulticastNALabel);
- // If to ff02::1, drop
+ // If to ff02::1, drop.
// TODO: Drop only if they don't contain the address of on-link neighbours.
gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
@@ -852,6 +858,7 @@
* <li>Pass all non-ICMPv6 IPv6 packets,
* <li>Pass all non-IPv4 and non-IPv6 packets,
* <li>Drop IPv6 ICMPv6 NAs to ff02::1.
+ * <li>Drop IPv6 ICMPv6 RSs.
* <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows
* insertion of RA filters here, or if there aren't any, just passes the packets.
* </ul>
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/GestureDescriptionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/GestureDescriptionTest.java
new file mode 100644
index 0000000..b876a5f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/GestureDescriptionTest.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2016 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.server.accessibility;
+
+import static android.accessibilityservice.GestureDescription.StrokeDescription.INVALID_STROKE_ID;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.everyItem;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.GestureStep;
+import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.graphics.Path;
+import android.graphics.PointF;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Test;
+
+import java.util.List;
+
+import static junit.framework.TestCase.assertEquals;
+
+/**
+ * Tests for GestureDescription
+ */
+public class GestureDescriptionTest {
+ @Test
+ public void testGestureShorterThanSampleRate_producesStartAndEnd() {
+ PointF click = new PointF(10, 20);
+ Path clickPath = new Path();
+ clickPath.moveTo(click.x, click.y);
+ StrokeDescription clickStroke = new StrokeDescription(clickPath, 0, 10);
+ GestureDescription.Builder clickBuilder = new GestureDescription.Builder();
+ clickBuilder.addStroke(clickStroke);
+ GestureDescription clickGesture = clickBuilder.build();
+
+ List<GestureStep> clickGestureSteps = MotionEventGenerator
+ .getGestureStepsFromGestureDescription(clickGesture, 100);
+
+ assertEquals(2, clickGestureSteps.size());
+ assertThat(clickGestureSteps.get(0), allOf(numTouchPointsIs(1), numStartsOfStroke(1),
+ numEndsOfStroke(0), hasPoint(click)));
+ assertThat(clickGestureSteps.get(1), allOf(numTouchPointsIs(1), numStartsOfStroke(0),
+ numEndsOfStroke(1), hasPoint(click)));
+ }
+
+ @Test
+ public void testSwipe_shouldContainEvenlySpacedPoints() {
+ int samplePeriod = 10;
+ int numSamples = 5;
+ float stepX = 2;
+ float stepY = 3;
+ PointF start = new PointF(10, 20);
+ PointF end = new PointF(10 + numSamples * stepX, 20 + numSamples * stepY);
+
+ GestureDescription swipe =
+ createSwipe(start.x, start.y, end.x, end.y, numSamples * samplePeriod);
+ List<GestureStep> swipeGestureSteps = MotionEventGenerator
+ .getGestureStepsFromGestureDescription(swipe, samplePeriod);
+ assertEquals(numSamples + 1, swipeGestureSteps.size());
+
+ assertThat(swipeGestureSteps.get(0), allOf(numTouchPointsIs(1), numStartsOfStroke(1),
+ numEndsOfStroke(0), hasPoint(start)));
+ assertThat(swipeGestureSteps.get(numSamples), allOf(numTouchPointsIs(1),
+ numStartsOfStroke(0), numEndsOfStroke(1), hasPoint(end)));
+
+ for (int i = 1; i < numSamples; ++i) {
+ PointF interpPoint = new PointF(start.x + stepX * i, start.y + stepY * i);
+ assertThat(swipeGestureSteps.get(i), allOf(numTouchPointsIs(1),
+ numStartsOfStroke(0), numEndsOfStroke(0), hasPoint(interpPoint)));
+ }
+ }
+
+ @Test
+ public void testSwipeWithNonIntegerValues_shouldRound() {
+ int strokeTime = 10;
+
+ GestureDescription swipe = createSwipe(10.1f, 20.6f, 11.9f, 22.1f, strokeTime);
+ List<GestureStep> swipeGestureSteps = MotionEventGenerator
+ .getGestureStepsFromGestureDescription(swipe, strokeTime);
+ assertEquals(2, swipeGestureSteps.size());
+ assertThat(swipeGestureSteps.get(0), hasPoint(new PointF(10, 21)));
+ assertThat(swipeGestureSteps.get(1), hasPoint(new PointF(12, 22)));
+ }
+
+ @Test
+ public void testPathsWithOverlappingTiming_produceCorrectSteps() {
+ // There are 4 paths
+ // 0: an L-shaped path that starts first
+ // 1: a swipe that starts in the middle of the L-shaped path and ends when the L ends
+ // 2: a swipe that starts at the same time as #1 but extends past the end of the L
+ // 3: a swipe that starts when #3 ends
+ PointF path0Start = new PointF(100, 150);
+ PointF path0Turn = new PointF(100, 200);
+ PointF path0End = new PointF(250, 200);
+ int path0StartTime = 0;
+ int path0EndTime = 100;
+ int path0Duration = path0EndTime - path0StartTime;
+ Path path0 = new Path();
+ path0.moveTo(path0Start.x, path0Start.y);
+ path0.lineTo(path0Turn.x, path0Turn.y);
+ path0.lineTo(path0End.x, path0End.y);
+ StrokeDescription path0Stroke = new StrokeDescription(path0, path0StartTime, path0Duration);
+
+ PointF path1Start = new PointF(300, 350);
+ PointF path1End = new PointF(300, 400);
+ int path1StartTime = 50;
+ int path1EndTime = path0EndTime;
+ StrokeDescription path1Stroke = createSwipeStroke(
+ path1Start.x, path1Start.y, path1End.x, path1End.y, path1StartTime, path1EndTime);
+
+ PointF path2Start = new PointF(400, 450);
+ PointF path2End = new PointF(400, 500);
+ int path2StartTime = 50;
+ int path2EndTime = 150;
+ StrokeDescription path2Stroke = createSwipeStroke(
+ path2Start.x, path2Start.y, path2End.x, path2End.y, path2StartTime, path2EndTime);
+
+ PointF path3Start = new PointF(500, 550);
+ PointF path3End = new PointF(500, 600);
+ int path3StartTime = path2EndTime;
+ int path3EndTime = 200;
+ StrokeDescription path3Stroke = createSwipeStroke(
+ path3Start.x, path3Start.y, path3End.x, path3End.y, path3StartTime, path3EndTime);
+
+ int deltaT = 12; // Force samples to happen on extra boundaries
+ GestureDescription.Builder builder = new GestureDescription.Builder();
+ builder.addStroke(path0Stroke);
+ builder.addStroke(path1Stroke);
+ builder.addStroke(path2Stroke);
+ builder.addStroke(path3Stroke);
+ List<GestureStep> steps = MotionEventGenerator
+ .getGestureStepsFromGestureDescription(builder.build(), deltaT);
+
+ long start = 0;
+ assertThat(steps.get(0), allOf(numStartsOfStroke(1), numEndsOfStroke(0), isAtTime(start),
+ numTouchPointsIs(1), hasPoint(path0Start)));
+ assertThat(steps.get(1), allOf(numTouchPointsIs(1), noStartsOrEnds(),
+ isAtTime(start + deltaT)));
+ assertThat(steps.get(2), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 2)));
+ assertThat(steps.get(3), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 3)));
+ assertThat(steps.get(4), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 4)));
+
+ assertThat(steps.get(5), allOf(numTouchPointsIs(3), numStartsOfStroke(2),
+ numEndsOfStroke(0), isAtTime(path1StartTime), hasPoint(path1Start),
+ hasPoint(path2Start)));
+
+ start = path1StartTime;
+ assertThat(steps.get(6), allOf(numTouchPointsIs(3), isAtTime(start + deltaT * 1)));
+ assertThat(steps.get(7), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 2)));
+ assertThat(steps.get(8), allOf(numTouchPointsIs(3), isAtTime(start + deltaT * 3)));
+ assertThat(steps.get(9), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 4)));
+
+ assertThat(steps.get(10), allOf(numTouchPointsIs(3), numStartsOfStroke(0),
+ numEndsOfStroke(2), isAtTime(path0EndTime), hasPoint(path0End),
+ hasPoint(path1End)));
+
+ start = path0EndTime;
+ assertThat(steps.get(11), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 1)));
+ assertThat(steps.get(12), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 2)));
+ assertThat(steps.get(13), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 3)));
+ assertThat(steps.get(14), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 4)));
+
+ assertThat(steps.get(15), allOf(numTouchPointsIs(2), numStartsOfStroke(1),
+ numEndsOfStroke(1), isAtTime(path2EndTime), hasPoint(path2End),
+ hasPoint(path3Start)));
+
+ start = path2EndTime;
+ assertThat(steps.get(16), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 1)));
+ assertThat(steps.get(17), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 2)));
+ assertThat(steps.get(18), allOf(numTouchPointsIs(1), isAtTime(start + deltaT * 3)));
+ assertThat(steps.get(19), allOf(noStartsOrEnds(), isAtTime(start + deltaT * 4)));
+
+ assertThat(steps.get(20), allOf(numTouchPointsIs(1), numStartsOfStroke(0),
+ numEndsOfStroke(1), isAtTime(path3EndTime), hasPoint(path3End)));
+ }
+
+ @Test
+ public void testMaxTouchpoints_shouldHaveValidCoords() {
+ GestureDescription.Builder maxPointBuilder = new GestureDescription.Builder();
+ PointF baseStartPoint = new PointF(100, 100);
+ PointF baseEndPoint = new PointF(100, 200);
+ int xStep = 10;
+ int samplePeriod = 15;
+ int numSamples = 2;
+ int numPoints = GestureDescription.getMaxStrokeCount();
+ for (int i = 0; i < numPoints; i++) {
+ Path path = new Path();
+ path.moveTo(baseStartPoint.x + i * xStep, baseStartPoint.y);
+ path.lineTo(baseEndPoint.x + i * xStep, baseEndPoint.y);
+ maxPointBuilder.addStroke(new StrokeDescription(path, 0, samplePeriod * numSamples));
+ }
+
+ List<GestureStep> steps = MotionEventGenerator
+ .getGestureStepsFromGestureDescription(maxPointBuilder.build(), samplePeriod);
+ assertEquals(3, steps.size());
+
+ assertThat(steps.get(0), allOf(numTouchPointsIs(numPoints), numStartsOfStroke(numPoints),
+ numEndsOfStroke(0), isAtTime(0)));
+ assertThat(steps.get(1), allOf(numTouchPointsIs(numPoints), numStartsOfStroke(0),
+ numEndsOfStroke(0), isAtTime(samplePeriod)));
+ assertThat(steps.get(2), allOf(numTouchPointsIs(numPoints), numStartsOfStroke(0),
+ numEndsOfStroke(numPoints), isAtTime(samplePeriod * 2)));
+
+ PointF baseMidPoint = new PointF((baseStartPoint.x + baseEndPoint.x) / 2,
+ (baseStartPoint.y + baseEndPoint.y) / 2);
+ for (int i = 0; i < numPoints; i++) {
+ assertThat(steps.get(0),
+ hasPoint(new PointF(baseStartPoint.x + i * xStep, baseStartPoint.y)));
+ assertThat(steps.get(1),
+ hasPoint(new PointF(baseMidPoint.x + i * xStep, baseMidPoint.y)));
+ assertThat(steps.get(2),
+ hasPoint(new PointF(baseEndPoint.x + i * xStep, baseEndPoint.y)));
+ }
+ }
+
+ @Test
+ public void testGetGestureSteps_touchPointsHaveStrokeId() {
+ StrokeDescription swipeStroke = createSwipeStroke(10, 20, 30, 40, 0, 100);
+ GestureDescription swipe = new GestureDescription.Builder().addStroke(swipeStroke).build();
+ List<GestureStep> swipeGestureSteps = MotionEventGenerator
+ .getGestureStepsFromGestureDescription(swipe, 10);
+
+ assertThat(swipeGestureSteps, everyItem(hasStrokeId(swipeStroke.getId())));
+ }
+
+ @Test
+ public void testGetGestureSteps_continuedStroke_hasNoEndPoint() {
+ Path swipePath = new Path();
+ swipePath.moveTo(10, 20);
+ swipePath.lineTo(30, 40);
+ StrokeDescription stroke1 =
+ new StrokeDescription(swipePath, 0, 100, 0, true);
+ GestureDescription gesture = new GestureDescription.Builder().addStroke(stroke1).build();
+ List<GestureStep> steps = MotionEventGenerator
+ .getGestureStepsFromGestureDescription(gesture, 10);
+
+ assertThat(steps, everyItem(numEndsOfStroke(0)));
+ }
+
+ @Test
+ public void testGetGestureSteps_continuingStroke_hasNoStartPointAndHasContinuedId() {
+ Path swipePath = new Path();
+ swipePath.moveTo(10, 20);
+ swipePath.lineTo(30, 40);
+ StrokeDescription stroke1 =
+ new StrokeDescription(swipePath, 0, 100, INVALID_STROKE_ID, true);
+ StrokeDescription stroke2 =
+ new StrokeDescription(swipePath, 0, 100, stroke1.getId(), false);
+ GestureDescription gesture = new GestureDescription.Builder().addStroke(stroke2).build();
+ List<GestureStep> steps = MotionEventGenerator
+ .getGestureStepsFromGestureDescription(gesture, 10);
+
+ assertThat(steps, everyItem(
+ allOf(continuesStrokeId(stroke1.getId()), numStartsOfStroke(0))));
+ }
+
+ private GestureDescription createSwipe(
+ float startX, float startY, float endX, float endY, long duration) {
+ GestureDescription.Builder swipeBuilder = new GestureDescription.Builder();
+ swipeBuilder.addStroke(createSwipeStroke(startX, startY, endX, endY, 0, duration));
+ return swipeBuilder.build();
+ }
+
+ private StrokeDescription createSwipeStroke(
+ float startX, float startY, float endX, float endY, long startTime, long endTime) {
+ Path swipePath = new Path();
+ swipePath.moveTo(startX, startY);
+ swipePath.lineTo(endX, endY);
+ StrokeDescription swipeStroke =
+ new StrokeDescription(swipePath, startTime, endTime - startTime);
+ return swipeStroke;
+ }
+
+ Matcher<GestureStep> numTouchPointsIs(final int numTouchPoints) {
+ return new TypeSafeMatcher<GestureStep>() {
+ @Override
+ protected boolean matchesSafely(GestureStep gestureStep) {
+ return gestureStep.numTouchPoints == numTouchPoints;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Has " + numTouchPoints + " touch point(s)");
+ }
+ };
+ }
+
+ Matcher<GestureStep> numStartsOfStroke(final int numStarts) {
+ return new TypeSafeMatcher<GestureStep>() {
+ @Override
+ protected boolean matchesSafely(GestureStep gestureStep) {
+ int numStartsFound = 0;
+ for (int i = 0; i < gestureStep.numTouchPoints; i++) {
+ if (gestureStep.touchPoints[i].mIsStartOfPath) {
+ numStartsFound++;
+ }
+ }
+ return numStartsFound == numStarts;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Starts " + numStarts + " stroke(s)");
+ }
+ };
+ }
+
+ Matcher<GestureStep> numEndsOfStroke(final int numEnds) {
+ return new TypeSafeMatcher<GestureStep>() {
+ @Override
+ protected boolean matchesSafely(GestureStep gestureStep) {
+ int numEndsFound = 0;
+ for (int i = 0; i < gestureStep.numTouchPoints; i++) {
+ if (gestureStep.touchPoints[i].mIsEndOfPath) {
+ numEndsFound++;
+ }
+ }
+ return numEndsFound == numEnds;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Ends " + numEnds + " stroke(s)");
+ }
+ };
+ }
+
+ Matcher<GestureStep> hasPoint(final PointF point) {
+ return new TypeSafeMatcher<GestureStep>() {
+ @Override
+ protected boolean matchesSafely(GestureStep gestureStep) {
+ for (int i = 0; i < gestureStep.numTouchPoints; i++) {
+ if ((gestureStep.touchPoints[i].mX == point.x)
+ && (gestureStep.touchPoints[i].mY == point.y)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Has at least one point at " + point);
+ }
+ };
+ }
+
+ Matcher<GestureStep> hasStrokeId(final int strokeId) {
+ return new TypeSafeMatcher<GestureStep>() {
+ @Override
+ protected boolean matchesSafely(GestureStep gestureStep) {
+ for (int i = 0; i < gestureStep.numTouchPoints; i++) {
+ if (gestureStep.touchPoints[i].mStrokeId == strokeId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Has at least one point with stroke id " + strokeId);
+ }
+ };
+ }
+
+ Matcher<GestureStep> continuesStrokeId(final int strokeId) {
+ return new TypeSafeMatcher<GestureStep>() {
+ @Override
+ protected boolean matchesSafely(GestureStep gestureStep) {
+ for (int i = 0; i < gestureStep.numTouchPoints; i++) {
+ if (gestureStep.touchPoints[i].mContinuedStrokeId == strokeId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Continues stroke id " + strokeId);
+ }
+ };
+ }
+
+ Matcher<GestureStep> isAtTime(final long time) {
+ return new TypeSafeMatcher<GestureStep>() {
+ @Override
+ protected boolean matchesSafely(GestureStep gestureStep) {
+ return gestureStep.timeSinceGestureStart == time;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Is at time " + time);
+ }
+ };
+ }
+
+ Matcher<GestureStep> noStartsOrEnds() {
+ return allOf(numStartsOfStroke(0), numEndsOfStroke(0));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index d5305d9..73344e0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -16,9 +16,17 @@
package com.android.server.accessibility;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowManagerPolicy.FLAG_PASS_TO_USER;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.everyItem;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
@@ -29,7 +37,10 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.accessibilityservice.GestureDescription.GestureStep;
+import android.accessibilityservice.GestureDescription.TouchPoint;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.graphics.Point;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -40,11 +51,15 @@
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.WindowManagerPolicy;
+
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import android.view.accessibility.AccessibilityEvent;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -58,30 +73,59 @@
@RunWith(AndroidJUnit4.class)
public class MotionEventInjectorTest {
private static final String LOG_TAG = "MotionEventInjectorTest";
- private static final int CLICK_X = 100;
- private static final int CLICK_Y_START = 200;
- private static final int CLICK_Y_END = 201;
- private static final int CLICK_DURATION = 10;
- private static final int SEQUENCE = 50;
+ private static final Matcher<MotionEvent> IS_ACTION_DOWN =
+ new MotionEventActionMatcher(ACTION_DOWN);
+ private static final Matcher<MotionEvent> IS_ACTION_POINTER_DOWN =
+ new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_DOWN);
+ private static final Matcher<MotionEvent> IS_ACTION_UP =
+ new MotionEventActionMatcher(ACTION_UP);
+ private static final Matcher<MotionEvent> IS_ACTION_POINTER_UP =
+ new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_UP);
+ private static final Matcher<MotionEvent> IS_ACTION_CANCEL =
+ new MotionEventActionMatcher(MotionEvent.ACTION_CANCEL);
+ private static final Matcher<MotionEvent> IS_ACTION_MOVE =
+ new MotionEventActionMatcher(MotionEvent.ACTION_MOVE);
- private static final int SECOND_CLICK_X = 1000;
- private static final int SECOND_CLICK_Y = 2000;
- private static final int SECOND_SEQUENCE = 51;
+ private static final Point LINE_START = new Point(100, 200);
+ private static final Point LINE_END = new Point(100, 300);
+ private static final int LINE_DURATION = 100;
+ private static final int LINE_SEQUENCE = 50;
+
+ private static final Point CLICK_POINT = new Point(1000, 2000);
+ private static final int CLICK_DURATION = 10;
+ private static final int CLICK_SEQUENCE = 51;
private static final int MOTION_EVENT_SOURCE = InputDevice.SOURCE_TOUCHSCREEN;
private static final int OTHER_EVENT_SOURCE = InputDevice.SOURCE_MOUSE;
+ private static final Point CONTINUED_LINE_START = new Point(500, 300);
+ private static final Point CONTINUED_LINE_MID1 = new Point(500, 400);
+ private static final Point CONTINUED_LINE_MID2 = new Point(600, 300);
+ private static final Point CONTINUED_LINE_END = new Point(600, 400);
+ private static final int CONTINUED_LINE_STROKE_ID_1 = 100;
+ private static final int CONTINUED_LINE_STROKE_ID_2 = 101;
+ private static final int CONTINUED_LINE_INTERVAL = 100;
+ private static final int CONTINUED_LINE_SEQUENCE_1 = 52;
+ private static final int CONTINUED_LINE_SEQUENCE_2 = 53;
+
MotionEventInjector mMotionEventInjector;
IAccessibilityServiceClient mServiceInterface;
- List<MotionEvent> mClickList = new ArrayList<>();
- List<MotionEvent> mSecondClickList = new ArrayList<>();
+ List<GestureStep> mLineList = new ArrayList<>();
+ List<GestureStep> mClickList = new ArrayList<>();
+ List<GestureStep> mContinuedLineList1 = new ArrayList<>();
+ List<GestureStep> mContinuedLineList2 = new ArrayList<>();
+
+ MotionEvent mClickDownEvent;
+ MotionEvent mClickUpEvent;
+
ArgumentCaptor<MotionEvent> mCaptor1 = ArgumentCaptor.forClass(MotionEvent.class);
ArgumentCaptor<MotionEvent> mCaptor2 = ArgumentCaptor.forClass(MotionEvent.class);
MessageCapturingHandler mMessageCapturingHandler;
- MotionEventMatcher mClickEvent0Matcher;
- MotionEventMatcher mClickEvent1Matcher;
- MotionEventMatcher mClickEvent2Matcher;
- MotionEventMatcher mSecondClickEvent0Matcher;
+ Matcher<MotionEvent> mIsLineStart;
+ Matcher<MotionEvent> mIsLineMiddle;
+ Matcher<MotionEvent> mIsLineEnd;
+ Matcher<MotionEvent> mIsClickDown;
+ Matcher<MotionEvent> mIsClickUp;
@BeforeClass
public static void oneTimeInitialization() {
@@ -99,226 +143,191 @@
}
});
mMotionEventInjector = new MotionEventInjector(mMessageCapturingHandler);
- mClickList.add(
- MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
- mClickList.add(MotionEvent.obtain(
- 0, CLICK_DURATION, MotionEvent.ACTION_MOVE, CLICK_X, CLICK_Y_END, 0));
- mClickList.add(MotionEvent.obtain(
- 0, CLICK_DURATION, MotionEvent.ACTION_UP, CLICK_X, CLICK_Y_END, 0));
- for (int i = 0; i < mClickList.size(); i++) {
- mClickList.get(i).setSource(MOTION_EVENT_SOURCE);
- }
-
- mClickEvent0Matcher = new MotionEventMatcher(mClickList.get(0));
- mClickEvent1Matcher = new MotionEventMatcher(mClickList.get(1));
- mClickEvent2Matcher = new MotionEventMatcher(mClickList.get(2));
-
- mSecondClickList.add(MotionEvent.obtain(
- 0, 0, MotionEvent.ACTION_DOWN, SECOND_CLICK_X, SECOND_CLICK_Y, 0));
- mSecondClickList.add(MotionEvent.obtain(
- 0, CLICK_DURATION, MotionEvent.ACTION_MOVE, SECOND_CLICK_X, CLICK_Y_END, 0));
- mSecondClickList.add(MotionEvent.obtain(
- 0, CLICK_DURATION, MotionEvent.ACTION_UP, SECOND_CLICK_X, CLICK_Y_END, 0));
- for (int i = 0; i < mSecondClickList.size(); i++) {
- mSecondClickList.get(i).setSource(MOTION_EVENT_SOURCE);
- }
-
- mSecondClickEvent0Matcher = new MotionEventMatcher(mSecondClickList.get(0));
-
mServiceInterface = mock(IAccessibilityServiceClient.class);
+
+ mLineList = createSimpleGestureFromPoints(0, 0, false, LINE_DURATION, LINE_START, LINE_END);
+ mClickList = createSimpleGestureFromPoints(
+ 0, 0, false, CLICK_DURATION, CLICK_POINT, CLICK_POINT);
+ mContinuedLineList1 = createSimpleGestureFromPoints(CONTINUED_LINE_STROKE_ID_1, 0, true,
+ CONTINUED_LINE_INTERVAL, CONTINUED_LINE_START, CONTINUED_LINE_MID1);
+ mContinuedLineList2 = createSimpleGestureFromPoints(CONTINUED_LINE_STROKE_ID_2,
+ CONTINUED_LINE_STROKE_ID_1, false, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_MID1,
+ CONTINUED_LINE_MID2, CONTINUED_LINE_END);
+
+ mClickDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, CLICK_POINT.x, CLICK_POINT.y, 0);
+ mClickDownEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ mClickUpEvent = MotionEvent.obtain(0, CLICK_DURATION, ACTION_UP, CLICK_POINT.x,
+ CLICK_POINT.y, 0);
+ mClickUpEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+
+ mIsLineStart = allOf(IS_ACTION_DOWN, isAtPoint(LINE_START), hasStandardInitialization(),
+ hasTimeFromDown(0));
+ mIsLineMiddle = allOf(IS_ACTION_MOVE, isAtPoint(LINE_END), hasStandardInitialization(),
+ hasTimeFromDown(LINE_DURATION));
+ mIsLineEnd = allOf(IS_ACTION_UP, isAtPoint(LINE_END), hasStandardInitialization(),
+ hasTimeFromDown(LINE_DURATION));
+ mIsClickDown = allOf(IS_ACTION_DOWN, isAtPoint(CLICK_POINT), hasStandardInitialization(),
+ hasTimeFromDown(0));
+ mIsClickUp = allOf(IS_ACTION_UP, isAtPoint(CLICK_POINT), hasStandardInitialization(),
+ hasTimeFromDown(CLICK_DURATION));
}
@Test
public void testInjectEvents_shouldEmergeInOrderWithCorrectTiming() throws RemoteException {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
verifyNoMoreInteractions(next);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
- eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
- long gestureStart = mCaptor1.getValue().getDownTime();
- mClickEvent0Matcher.offsetTimesBy(gestureStart);
- mClickEvent1Matcher.offsetTimesBy(gestureStart);
- mClickEvent2Matcher.offsetTimesBy(gestureStart);
-
- verify(next).onMotionEvent(argThat(mClickEvent0Matcher), argThat(mClickEvent0Matcher),
- eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
+ verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(FLAG_PASS_TO_USER));
+ verify(next).onMotionEvent(argThat(mIsLineStart), argThat(mIsLineStart),
+ eq(FLAG_PASS_TO_USER));
verifyNoMoreInteractions(next);
reset(next);
+ Matcher<MotionEvent> hasRightDownTime = hasDownTime(mCaptor1.getValue().getDownTime());
+
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- verify(next).onMotionEvent(argThat(mClickEvent1Matcher), argThat(mClickEvent1Matcher),
- eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
+ verify(next).onMotionEvent(argThat(allOf(mIsLineMiddle, hasRightDownTime)),
+ argThat(allOf(mIsLineMiddle, hasRightDownTime)), eq(FLAG_PASS_TO_USER));
verifyNoMoreInteractions(next);
reset(next);
verifyZeroInteractions(mServiceInterface);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- verify(next).onMotionEvent(argThat(mClickEvent2Matcher), argThat(mClickEvent2Matcher),
- eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
+ verify(next).onMotionEvent(argThat(allOf(mIsLineEnd, hasRightDownTime)),
+ argThat(allOf(mIsLineEnd, hasRightDownTime)), eq(FLAG_PASS_TO_USER));
verifyNoMoreInteractions(next);
- reset(next);
- verify(mServiceInterface).onPerformGestureResult(SEQUENCE, true);
+ verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
verifyNoMoreInteractions(mServiceInterface);
}
@Test
- public void testInjectEvents_eventWithManyPointers_shouldNotCrash() {
- int manyPointersCount = 20;
- MotionEvent.PointerCoords[] pointerCoords =
- new MotionEvent.PointerCoords[manyPointersCount];
- MotionEvent.PointerProperties[] pointerProperties =
- new MotionEvent.PointerProperties[manyPointersCount];
- for (int i = 0; i < manyPointersCount; i++) {
- pointerProperties[i] = new MotionEvent.PointerProperties();
- pointerProperties[i].id = i;
- pointerProperties[i].toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
- pointerCoords[i] = new MotionEvent.PointerCoords();
- pointerCoords[i].clear();
- pointerCoords[i].pressure = 1.0f;
- pointerCoords[i].size = 1.0f;
- pointerCoords[i].x = i;
- pointerCoords[i].y = i;
+ public void testInjectEvents_gestureWithTooManyPoints_shouldNotCrash() throws Exception {
+ int tooManyPointsCount = 20;
+ TouchPoint[] startTouchPoints = new TouchPoint[tooManyPointsCount];
+ TouchPoint[] endTouchPoints = new TouchPoint[tooManyPointsCount];
+ for (int i = 0; i < tooManyPointsCount; i++) {
+ startTouchPoints[i] = new TouchPoint();
+ startTouchPoints[i].mIsStartOfPath = true;
+ startTouchPoints[i].mX = i;
+ startTouchPoints[i].mY = i;
+ endTouchPoints[i] = new TouchPoint();
+ endTouchPoints[i].mIsEndOfPath = true;
+ endTouchPoints[i].mX = i;
+ endTouchPoints[i].mY = i;
}
- List<MotionEvent> events = new ArrayList<>();
- events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, manyPointersCount,
- pointerProperties, pointerCoords, 0, 0,
- 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0));
- events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, manyPointersCount,
- pointerProperties, pointerCoords, 0, 0,
- 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0));
- EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.injectEvents(events, mServiceInterface, SEQUENCE);
+ List<GestureStep> events = Arrays.asList(
+ new GestureStep(0, tooManyPointsCount, startTouchPoints),
+ new GestureStep(CLICK_DURATION, tooManyPointsCount, endTouchPoints));
+ attachMockNext(mMotionEventInjector);
+ injectEventsSync(events, mServiceInterface, CLICK_SEQUENCE);
mMessageCapturingHandler.sendAllMessages();
- verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- assertEquals(MotionEvent.ACTION_DOWN, mCaptor1.getAllValues().get(0).getActionMasked());
- assertEquals(MotionEvent.ACTION_UP, mCaptor1.getAllValues().get(1).getActionMasked());
+ verify(mServiceInterface).onPerformGestureResult(eq(CLICK_SEQUENCE), anyBoolean());
}
@Test
public void testRegularEvent_afterGestureComplete_shouldPassToNext() {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
mMessageCapturingHandler.sendAllMessages(); // Send all motion events
reset(next);
- mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mClickList.get(0), 0);
- verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
- argThat(mClickEvent0Matcher), eq(0));
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
+ verify(next).onMotionEvent(argThat(mIsClickDown), argThat(mIsClickDown), eq(0));
}
@Test
public void testInjectEvents_withRealGestureUnderway_shouldCancelRealAndPassInjected() {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
- mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
- assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
+ assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
+ assertThat(mCaptor1.getAllValues().get(1), IS_ACTION_CANCEL);
reset(next);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
- eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
- long gestureStart = mCaptor1.getValue().getDownTime();
- mSecondClickEvent0Matcher.offsetTimesBy(gestureStart);
-
- verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
- argThat(mSecondClickEvent0Matcher), eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
+ verify(next).onMotionEvent(
+ argThat(mIsLineStart), argThat(mIsLineStart), eq(FLAG_PASS_TO_USER));
}
@Test
public void testInjectEvents_withRealMouseGestureUnderway_shouldContinueRealAndPassInjected() {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- MotionEvent mouseEvent = MotionEvent.obtain(mClickList.get(0));
+ MotionEvent mouseEvent = MotionEvent.obtain(mClickDownEvent);
mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
- MotionEventMatcher mouseEventMatcher = new MotionEventMatcher(mouseEvent);
+ MotionEventMatcher isMouseEvent = new MotionEventMatcher(mouseEvent);
mMotionEventInjector.onMotionEvent(mouseEvent, mouseEvent, 0);
- mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- assertTrue(mouseEventMatcher.matches(mCaptor1.getAllValues().get(0)));
- mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(1).getDownTime());
- assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(1)));
+ assertThat(mCaptor1.getAllValues().get(0), isMouseEvent);
+ assertThat(mCaptor1.getAllValues().get(1), mIsLineStart);
}
@Test
public void testInjectEvents_withRealGestureFinished_shouldJustPassInjected() {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
- mMotionEventInjector.onMotionEvent(mClickList.get(1), mClickList.get(1), 0);
- mMotionEventInjector.onMotionEvent(mClickList.get(2), mClickList.get(2), 0);
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
+ mMotionEventInjector.onMotionEvent(mClickUpEvent, mClickUpEvent, 0);
- mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
- verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
-
- assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
- assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
- assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
+ verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
+ assertThat(mCaptor1.getAllValues().get(1), mIsClickUp);
reset(next);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(),
- eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
- mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getValue().getDownTime());
- verify(next).onMotionEvent(argThat(mSecondClickEvent0Matcher),
- argThat(mSecondClickEvent0Matcher), eq(WindowManagerPolicy.FLAG_PASS_TO_USER));
+ verify(next).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), eq(FLAG_PASS_TO_USER));
+ verify(next).onMotionEvent(
+ argThat(mIsLineStart), argThat(mIsLineStart), eq(FLAG_PASS_TO_USER));
}
@Test
public void testOnMotionEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassReal()
throws RemoteException {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
-
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mSecondClickList.get(0), 0);
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- mClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(0).getDownTime());
- assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
- assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
- assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
- verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
+ assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
+ assertThat(mCaptor1.getAllValues().get(1), IS_ACTION_CANCEL);
+ assertThat(mCaptor1.getAllValues().get(2), mIsClickDown);
+ verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
}
@Test
public void testOnMotionEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassReal()
throws RemoteException {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mClickList.add(MotionEvent.obtain(2 * CLICK_DURATION, 2 * CLICK_DURATION,
- MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0));
- mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ // Tack a click down to the end of the line
+ TouchPoint clickTouchPoint = new TouchPoint();
+ clickTouchPoint.mIsStartOfPath = true;
+ clickTouchPoint.mX = CLICK_POINT.x;
+ clickTouchPoint.mY = CLICK_POINT.y;
+ mLineList.add(new GestureStep(0, 1, new TouchPoint[] {clickTouchPoint}));
+
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
// Send 3 motion events, leaving the extra down in the queue
mMessageCapturingHandler.sendOneMessage();
mMessageCapturingHandler.sendOneMessage();
mMessageCapturingHandler.sendOneMessage();
- mMotionEventInjector.onMotionEvent(mSecondClickList.get(0), mClickList.get(0), 0);
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- long gestureStart = mCaptor1.getAllValues().get(0).getDownTime();
- mClickEvent0Matcher.offsetTimesBy(gestureStart);
- mClickEvent1Matcher.offsetTimesBy(gestureStart);
- mClickEvent2Matcher.offsetTimesBy(gestureStart);
- assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
- assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
- assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
- assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(3)));
-
- verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
+ assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
+ assertThat(mCaptor1.getAllValues().get(1), mIsLineMiddle);
+ assertThat(mCaptor1.getAllValues().get(2), mIsLineEnd);
+ assertThat(mCaptor1.getAllValues().get(3), mIsClickDown);
+ verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
assertFalse(mMessageCapturingHandler.hasMessages());
}
@@ -326,105 +335,327 @@
public void testInjectEvents_openInjectedGestureInProgress_shouldCancelAndNotifyAndPassNew()
throws RemoteException {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
- mMessageCapturingHandler.sendLastMessage(); // Process the second event injection
+ injectEventsSync(mClickList, mServiceInterface, CLICK_SEQUENCE);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- verify(mServiceInterface, times(1)).onPerformGestureResult(SEQUENCE, false);
+ verify(mServiceInterface, times(1)).onPerformGestureResult(LINE_SEQUENCE, false);
verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- mClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(0).getDownTime());
- assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
- assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
- mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(2).getDownTime());
- assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
+ assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
+ assertThat(mCaptor1.getAllValues().get(1), IS_ACTION_CANCEL);
+ assertThat(mCaptor1.getAllValues().get(2), mIsClickDown);
}
@Test
public void testInjectEvents_closedInjectedGestureInProgress_shouldOnlyNotifyAndPassNew()
throws RemoteException {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- MotionEvent newEvent = MotionEvent.obtain(2 * CLICK_DURATION, 2 * CLICK_DURATION,
- MotionEvent.ACTION_DOWN, CLICK_X, CLICK_Y_START, 0);
- newEvent.setSource(mClickList.get(0).getSource());
- mClickList.add(newEvent);
- mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ // Tack a click down to the end of the line
+ TouchPoint clickTouchPoint = new TouchPoint();
+ clickTouchPoint.mIsStartOfPath = true;
+ clickTouchPoint.mX = CLICK_POINT.x;
+ clickTouchPoint.mY = CLICK_POINT.y;
+ mLineList.add(new GestureStep(0, 1, new TouchPoint[] {clickTouchPoint}));
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
// Send 3 motion events, leaving newEvent in the queue
mMessageCapturingHandler.sendOneMessage();
mMessageCapturingHandler.sendOneMessage();
mMessageCapturingHandler.sendOneMessage();
- mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
- mMessageCapturingHandler.sendLastMessage(); // Process the event injection
+ injectEventsSync(mClickList, mServiceInterface, CLICK_SEQUENCE);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
- verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
+ verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- long gestureStart = mCaptor1.getAllValues().get(0).getDownTime();
- mClickEvent0Matcher.offsetTimesBy(gestureStart);
- mClickEvent1Matcher.offsetTimesBy(gestureStart);
- mClickEvent2Matcher.offsetTimesBy(gestureStart);
- assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
- assertTrue(mClickEvent1Matcher.matches(mCaptor1.getAllValues().get(1)));
- assertTrue(mClickEvent2Matcher.matches(mCaptor1.getAllValues().get(2)));
- mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(3).getDownTime());
- assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(3)));
+ assertThat(mCaptor1.getAllValues().get(0), mIsLineStart);
+ assertThat(mCaptor1.getAllValues().get(1), mIsLineMiddle);
+ assertThat(mCaptor1.getAllValues().get(2), mIsLineEnd);
+ assertThat(mCaptor1.getAllValues().get(3), mIsClickDown);
+ }
+
+ @Test
+ public void testContinuedGesture_continuationArrivesAfterDispatched_gestureCompletes()
+ throws Exception {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
+ injectEventsSync(mContinuedLineList2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, true);
+ verify(next, times(5)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ long downTime = events.get(0).getDownTime();
+ assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN,
+ hasEventTime(downTime)));
+ assertThat(events, everyItem(hasDownTime(downTime)));
+ assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));
+ // Timing will restart when the gesture continues
+ long secondSequenceStart = events.get(2).getEventTime();
+ assertTrue(secondSequenceStart > events.get(1).getEventTime());
+ assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE));
+ assertThat(events.get(3), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_MOVE,
+ hasEventTime(secondSequenceStart + CONTINUED_LINE_INTERVAL)));
+ assertThat(events.get(4), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_UP,
+ hasEventTime(secondSequenceStart + CONTINUED_LINE_INTERVAL)));
+ }
+
+ @Test
+ public void testContinuedGesture_withTwoTouchPoints_gestureCompletes()
+ throws Exception {
+ // Run one point through the continued line backwards
+ int backLineId1 = 30;
+ int backLineId2 = 30;
+ List<GestureStep> continuedBackLineList1 = createSimpleGestureFromPoints(backLineId1, 0,
+ true, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_END, CONTINUED_LINE_MID2);
+ List<GestureStep> continuedBackLineList2 = createSimpleGestureFromPoints(backLineId2,
+ backLineId1, false, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_MID2,
+ CONTINUED_LINE_MID1, CONTINUED_LINE_START);
+ List<GestureStep> combinedLines1 = combineGestureSteps(
+ mContinuedLineList1, continuedBackLineList1);
+ List<GestureStep> combinedLines2 = combineGestureSteps(
+ mContinuedLineList2, continuedBackLineList2);
+
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(combinedLines1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ injectEventsSync(combinedLines2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, true);
+ verify(next, times(7)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ long downTime = events.get(0).getDownTime();
+ assertThat(events.get(0), allOf(
+ anyOf(isAtPoint(CONTINUED_LINE_END), isAtPoint(CONTINUED_LINE_START)),
+ IS_ACTION_DOWN, hasEventTime(downTime)));
+ assertThat(events, everyItem(hasDownTime(downTime)));
+ assertThat(events.get(1), allOf(containsPoints(CONTINUED_LINE_START, CONTINUED_LINE_END),
+ IS_ACTION_POINTER_DOWN, hasEventTime(downTime)));
+ assertThat(events.get(2), allOf(containsPoints(CONTINUED_LINE_MID1, CONTINUED_LINE_MID2),
+ IS_ACTION_MOVE, hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));
+ assertThat(events.get(3), allOf(containsPoints(CONTINUED_LINE_MID1, CONTINUED_LINE_MID2),
+ IS_ACTION_MOVE, hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 2)));
+ assertThat(events.get(4), allOf(containsPoints(CONTINUED_LINE_START, CONTINUED_LINE_END),
+ IS_ACTION_MOVE, hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
+ assertThat(events.get(5), allOf(containsPoints(CONTINUED_LINE_START, CONTINUED_LINE_END),
+ IS_ACTION_POINTER_UP, hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
+ assertThat(events.get(6), allOf(
+ anyOf(isAtPoint(CONTINUED_LINE_END), isAtPoint(CONTINUED_LINE_START)),
+ IS_ACTION_UP, hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
+ }
+
+
+ @Test
+ public void testContinuedGesture_continuationArrivesWhileDispatching_gestureCompletes()
+ throws Exception {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ mMessageCapturingHandler.sendOneMessage(); // Send a motion event
+ injectEventsSync(mContinuedLineList2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, true);
+ verify(next, times(5)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ long downTime = events.get(0).getDownTime();
+ assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN,
+ hasEventTime(downTime)));
+ assertThat(events, everyItem(hasDownTime(downTime)));
+ assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));
+ assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 2)));
+ assertThat(events.get(3), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_MOVE,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
+ assertThat(events.get(4), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_UP,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
+ }
+
+ @Test
+ public void testContinuedGesture_twoContinuationsArriveWhileDispatching_gestureCompletes()
+ throws Exception {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ // Continue line again
+ List<GestureStep> continuedLineList2 = createSimpleGestureFromPoints(
+ CONTINUED_LINE_STROKE_ID_2, CONTINUED_LINE_STROKE_ID_1, true,
+ CONTINUED_LINE_INTERVAL, CONTINUED_LINE_MID1,
+ CONTINUED_LINE_MID2, CONTINUED_LINE_END);
+ // Finish line by backtracking
+ int strokeId3 = CONTINUED_LINE_STROKE_ID_2 + 1;
+ int sequence3 = CONTINUED_LINE_SEQUENCE_2 + 1;
+ List<GestureStep> continuedLineList3 = createSimpleGestureFromPoints(strokeId3,
+ CONTINUED_LINE_STROKE_ID_2, false, CONTINUED_LINE_INTERVAL, CONTINUED_LINE_END,
+ CONTINUED_LINE_MID2);
+
+ mMessageCapturingHandler.sendOneMessage(); // Send a motion event
+ injectEventsSync(continuedLineList2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
+ injectEventsSync(continuedLineList3, mServiceInterface, sequence3);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, true);
+ verify(mServiceInterface).onPerformGestureResult(sequence3, true);
+ verify(next, times(6)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ long downTime = events.get(0).getDownTime();
+ assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN,
+ hasEventTime(downTime)));
+ assertThat(events, everyItem(hasDownTime(downTime)));
+ assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL)));
+ assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 2)));
+ assertThat(events.get(3), allOf(isAtPoint(CONTINUED_LINE_END), IS_ACTION_MOVE,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 3)));
+ assertThat(events.get(4), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_MOVE,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 4)));
+ assertThat(events.get(5), allOf(isAtPoint(CONTINUED_LINE_MID2), IS_ACTION_UP,
+ hasEventTime(downTime + CONTINUED_LINE_INTERVAL * 4)));
+ }
+
+ @Test
+ public void testContinuedGesture_nonContinuingGestureArrivesDuringDispatch_gestureCanceled()
+ throws Exception {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ mMessageCapturingHandler.sendOneMessage(); // Send a motion event
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, false);
+ verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
+ verify(next, times(5)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
+ assertThat(events.get(1), IS_ACTION_CANCEL);
+ assertThat(events.get(2), allOf(isAtPoint(LINE_START), IS_ACTION_DOWN));
+ assertThat(events.get(3), allOf(isAtPoint(LINE_END), IS_ACTION_MOVE));
+ assertThat(events.get(4), allOf(isAtPoint(LINE_END), IS_ACTION_UP));
+ }
+
+ @Test
+ public void testContinuedGesture_nonContinuingGestureArrivesAfterDispatch_gestureCanceled()
+ throws Exception {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
+ verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, true);
+ verify(next, times(6)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
+ assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE));
+ assertThat(events.get(2), IS_ACTION_CANCEL);
+ assertThat(events.get(3), allOf(isAtPoint(LINE_START), IS_ACTION_DOWN));
+ assertThat(events.get(4), allOf(isAtPoint(LINE_END), IS_ACTION_MOVE));
+ assertThat(events.get(5), allOf(isAtPoint(LINE_END), IS_ACTION_UP));
+ }
+
+ @Test
+ public void testContinuedGesture_misMatchedContinuationArrives_bothGesturesCanceled()
+ throws Exception {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
+ List<GestureStep> discontinuousGesture = mContinuedLineList2
+ .subList(1, mContinuedLineList2.size());
+ injectEventsSync(discontinuousGesture, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, false);
+ verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
+ assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE));
+ assertThat(events.get(2), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_CANCEL));
+ }
+
+ @Test
+ public void testContinuedGesture_continuationArrivesFromOtherService_bothGesturesCanceled()
+ throws Exception {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ IAccessibilityServiceClient otherService = mock(IAccessibilityServiceClient.class);
+ injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ mMessageCapturingHandler.sendOneMessage(); // Send a motion events
+ injectEventsSync(mContinuedLineList2, otherService, CONTINUED_LINE_SEQUENCE_2);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, false);
+ verify(otherService).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, false);
+ verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
+ assertThat(events.get(1), IS_ACTION_CANCEL);
+ }
+
+ @Test
+ public void testContinuedGesture_realGestureArrivesInBetween_getsCanceled()
+ throws Exception {
+ EventStreamTransformation next = attachMockNext(mMotionEventInjector);
+ injectEventsSync(mContinuedLineList1, mServiceInterface, CONTINUED_LINE_SEQUENCE_1);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_1, true);
+
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
+
+ injectEventsSync(mContinuedLineList2, mServiceInterface, CONTINUED_LINE_SEQUENCE_2);
+ mMessageCapturingHandler.sendAllMessages(); // Send all motion events
+ verify(mServiceInterface).onPerformGestureResult(CONTINUED_LINE_SEQUENCE_2, false);
+ verify(next, times(4)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
+ List<MotionEvent> events = mCaptor1.getAllValues();
+ assertThat(events.get(0), allOf(isAtPoint(CONTINUED_LINE_START), IS_ACTION_DOWN));
+ assertThat(events.get(1), allOf(isAtPoint(CONTINUED_LINE_MID1), IS_ACTION_MOVE));
+ assertThat(events.get(2), IS_ACTION_CANCEL);
+ assertThat(events.get(3), allOf(isAtPoint(CLICK_POINT), IS_ACTION_DOWN));
}
@Test
public void testClearEvents_realGestureInProgress_shouldForgetAboutGesture() {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
mMotionEventInjector.clearEvents(MOTION_EVENT_SOURCE);
- mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
verify(next, times(2)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
- mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(1).getDownTime());
- assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(1)));
+ assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
+ assertThat(mCaptor1.getAllValues().get(1), mIsLineStart);
}
@Test
public void testClearEventsOnOtherSource_realGestureInProgress_shouldNotForgetAboutGesture() {
EventStreamTransformation next = attachMockNext(mMotionEventInjector);
- mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
mMotionEventInjector.clearEvents(OTHER_EVENT_SOURCE);
- mMotionEventInjector.injectEvents(mSecondClickList, mServiceInterface, SECOND_SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
mMessageCapturingHandler.sendOneMessage(); // Send a motion event
verify(next, times(3)).onMotionEvent(mCaptor1.capture(), mCaptor2.capture(), anyInt());
- assertTrue(mClickEvent0Matcher.matches(mCaptor1.getAllValues().get(0)));
- assertEquals(MotionEvent.ACTION_CANCEL, mCaptor1.getAllValues().get(1).getActionMasked());
- mSecondClickEvent0Matcher.offsetTimesBy(mCaptor1.getAllValues().get(2).getDownTime());
- assertTrue(mSecondClickEvent0Matcher.matches(mCaptor1.getAllValues().get(2)));
+ assertThat(mCaptor1.getAllValues().get(0), mIsClickDown);
+ assertThat(mCaptor1.getAllValues().get(1), IS_ACTION_CANCEL);
+ assertThat(mCaptor1.getAllValues().get(2), mIsLineStart);
}
@Test
public void testOnDestroy_shouldCancelGestures() throws RemoteException {
mMotionEventInjector.onDestroy();
- mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
- verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
+ verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
}
@Test
public void testInjectEvents_withNoNext_shouldCancel() throws RemoteException {
- mMotionEventInjector.injectEvents(mClickList, mServiceInterface, SEQUENCE);
- mMessageCapturingHandler.sendOneMessage(); // Process the event injection
- verify(mServiceInterface).onPerformGestureResult(SEQUENCE, false);
+ injectEventsSync(mLineList, mServiceInterface, LINE_SEQUENCE);
+ verify(mServiceInterface).onPerformGestureResult(LINE_SEQUENCE, false);
}
@Test
public void testOnMotionEvent_withNoNext_shouldNotCrash() {
- mMotionEventInjector.onMotionEvent(mClickList.get(0), mClickList.get(0), 0);
+ mMotionEventInjector.onMotionEvent(mClickDownEvent, mClickDownEvent, 0);
}
@Test
@@ -455,6 +686,52 @@
mMotionEventInjector.onAccessibilityEvent(event);
}
+ private void injectEventsSync(List<GestureStep> gestureSteps,
+ IAccessibilityServiceClient serviceInterface, int sequence) {
+ mMotionEventInjector.injectEvents(gestureSteps, serviceInterface, sequence);
+ // Dispatch the message sent by the injector. Our simple handler doesn't guarantee stuff
+ // happens in order.
+ mMessageCapturingHandler.sendLastMessage();
+ }
+
+ private List<GestureStep> createSimpleGestureFromPoints(int strokeId, int continuedStrokeId,
+ boolean continued, long interval, Point... points) {
+ List<GestureStep> gesture = new ArrayList<>(points.length);
+ TouchPoint[] touchPoints = new TouchPoint[1];
+ touchPoints[0] = new TouchPoint();
+ for (int i = 0; i < points.length; i++) {
+ touchPoints[0].mX = points[i].x;
+ touchPoints[0].mY = points[i].y;
+ touchPoints[0].mIsStartOfPath = ((i == 0) && (continuedStrokeId <= 0));
+ touchPoints[0].mContinuedStrokeId = continuedStrokeId;
+ touchPoints[0].mStrokeId = strokeId;
+ touchPoints[0].mIsEndOfPath = ((i == points.length - 1) && !continued);
+ gesture.add(new GestureStep(interval * i, 1, touchPoints));
+ }
+ return gesture;
+ }
+
+ List<GestureStep> combineGestureSteps(List<GestureStep> list1, List<GestureStep> list2) {
+ assertEquals(list1.size(), list2.size());
+ List<GestureStep> gesture = new ArrayList<>(list1.size());
+ for (int i = 0; i < list1.size(); i++) {
+ int numPoints1 = list1.get(i).numTouchPoints;
+ int numPoints2 = list2.get(i).numTouchPoints;
+ TouchPoint[] touchPoints = new TouchPoint[numPoints1 + numPoints2];
+ for (int j = 0; j < numPoints1; j++) {
+ touchPoints[j] = new TouchPoint();
+ touchPoints[j].copyFrom(list1.get(i).touchPoints[j]);
+ }
+ for (int j = 0; j < numPoints2; j++) {
+ touchPoints[numPoints1 + j] = new TouchPoint();
+ touchPoints[numPoints1 + j].copyFrom(list2.get(i).touchPoints[j]);
+ }
+ gesture.add(new GestureStep(list1.get(i).timeSinceGestureStart,
+ numPoints1 + numPoints2, touchPoints));
+ }
+ return gesture;
+ }
+
private EventStreamTransformation attachMockNext(MotionEventInjector motionEventInjector) {
EventStreamTransformation next = mock(EventStreamTransformation.class);
motionEventInjector.setNext(next);
@@ -506,4 +783,126 @@
return false;
}
}
+
+ private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
+ int mAction;
+
+ MotionEventActionMatcher(int action) {
+ super();
+ mAction = action;
+ }
+
+ @Override
+ protected boolean matchesSafely(MotionEvent motionEvent) {
+ return motionEvent.getActionMasked() == mAction;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to action " + mAction);
+ }
+ }
+
+ private static TypeSafeMatcher<MotionEvent> isAtPoint(final Point point) {
+ return new TypeSafeMatcher<MotionEvent>() {
+ @Override
+ protected boolean matchesSafely(MotionEvent event) {
+ return ((event.getX() == point.x) && (event.getY() == point.y));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Is at point " + point);
+ }
+ };
+ }
+
+ private static TypeSafeMatcher<MotionEvent> containsPoints(final Point... points) {
+ return new TypeSafeMatcher<MotionEvent>() {
+ @Override
+ protected boolean matchesSafely(MotionEvent event) {
+ MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+ for (int i = 0; i < points.length; i++) {
+ boolean havePoint = false;
+ for (int j = 0; j < points.length; j++) {
+ event.getPointerCoords(j, coords);
+ if ((points[i].x == coords.x) && (points[i].y == coords.y)) {
+ havePoint = true;
+ }
+ }
+ if (!havePoint) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Contains points " + points);
+ }
+ };
+ }
+
+ private static TypeSafeMatcher<MotionEvent> hasDownTime(final long downTime) {
+ return new TypeSafeMatcher<MotionEvent>() {
+ @Override
+ protected boolean matchesSafely(MotionEvent event) {
+ return event.getDownTime() == downTime;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Down time = " + downTime);
+ }
+ };
+ }
+
+ private static TypeSafeMatcher<MotionEvent> hasEventTime(final long eventTime) {
+ return new TypeSafeMatcher<MotionEvent>() {
+ @Override
+ protected boolean matchesSafely(MotionEvent event) {
+ return event.getEventTime() == eventTime;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Event time = " + eventTime);
+ }
+ };
+ }
+
+ private static TypeSafeMatcher<MotionEvent> hasTimeFromDown(final long timeFromDown) {
+ return new TypeSafeMatcher<MotionEvent>() {
+ @Override
+ protected boolean matchesSafely(MotionEvent event) {
+ return (event.getEventTime() - event.getDownTime()) == timeFromDown;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Time from down to event times = " + timeFromDown);
+ }
+ };
+ }
+
+ private static TypeSafeMatcher<MotionEvent> hasStandardInitialization() {
+ return new TypeSafeMatcher<MotionEvent>() {
+ @Override
+ protected boolean matchesSafely(MotionEvent event) {
+ return (0 == event.getActionIndex()) && (0 == event.getDeviceId())
+ && (0 == event.getEdgeFlags()) && (0 == event.getFlags())
+ && (0 == event.getMetaState()) && (0F == event.getOrientation())
+ && (0F == event.getTouchMajor()) && (0F == event.getTouchMinor())
+ && (1F == event.getXPrecision()) && (1F == event.getYPrecision())
+ && (1 == event.getPointerCount()) && (1F == event.getPressure())
+ && (InputDevice.SOURCE_TOUCHSCREEN == event.getSource());
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Has standard values for all parameters");
+ }
+ };
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 0801a88..162a1a9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -25,14 +25,7 @@
import java.util.ArrayList;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static org.junit.Assert.assertEquals;
/**
@@ -48,21 +41,8 @@
@Test
public void testForAllWindows() throws Exception {
- final DisplayContent dc = new DisplayContent(mDisplay, sWm, null, null);
- final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, dc, "wallpaper");
- final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, dc, "ime");
- final WindowState imeDialogWindow = createWindow(null, TYPE_INPUT_METHOD_DIALOG, dc,
- "ime dialog");
- final WindowState statusBarWindow = createWindow(null, TYPE_STATUS_BAR, dc, "status bar");
- final WindowState navBarWindow = createWindow(null, TYPE_NAVIGATION_BAR,
- statusBarWindow.mToken, "nav bar");
- final WindowState appWindow = createWindow(null, TYPE_BASE_APPLICATION, dc, "app");
- final WindowState negChildAppWindow = createWindow(appWindow, TYPE_APPLICATION_MEDIA,
- appWindow.mToken, "negative app child");
- final WindowState posChildAppWindow = createWindow(appWindow,
- TYPE_APPLICATION_ATTACHED_DIALOG, appWindow.mToken, "positive app child");
- final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION, dc,
- "exiting app");
+ final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION,
+ sDisplayContent, "exiting app");
final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken;
exitingAppToken.mIsExiting = true;
exitingAppToken.mTask.mStack.mExitingAppTokens.add(exitingAppToken);
@@ -70,30 +50,75 @@
final ArrayList<WindowState> windows = new ArrayList();
// Test forward traversal.
- dc.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
+ sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
- assertEquals(wallpaperWindow, windows.get(0));
+ assertEquals(sWallpaperWindow, windows.get(0));
assertEquals(exitingAppWindow, windows.get(1));
- assertEquals(negChildAppWindow, windows.get(2));
- assertEquals(appWindow, windows.get(3));
- assertEquals(posChildAppWindow, windows.get(4));
- assertEquals(statusBarWindow, windows.get(5));
- assertEquals(navBarWindow, windows.get(6));
- assertEquals(imeWindow, windows.get(7));
- assertEquals(imeDialogWindow, windows.get(8));
+ assertEquals(sChildAppWindowBelow, windows.get(2));
+ assertEquals(sAppWindow, windows.get(3));
+ assertEquals(sChildAppWindowAbove, windows.get(4));
+ assertEquals(sDockedDividerWindow, windows.get(5));
+ assertEquals(sStatusBarWindow, windows.get(6));
+ assertEquals(sNavBarWindow, windows.get(7));
+ assertEquals(sImeWindow, windows.get(8));
+ assertEquals(sImeDialogWindow, windows.get(9));
// Test backward traversal.
windows.clear();
- dc.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
+ sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
- assertEquals(wallpaperWindow, windows.get(8));
- assertEquals(exitingAppWindow, windows.get(7));
- assertEquals(negChildAppWindow, windows.get(6));
- assertEquals(appWindow, windows.get(5));
- assertEquals(posChildAppWindow, windows.get(4));
- assertEquals(statusBarWindow, windows.get(3));
- assertEquals(navBarWindow, windows.get(2));
- assertEquals(imeWindow, windows.get(1));
- assertEquals(imeDialogWindow, windows.get(0));
+ assertEquals(sWallpaperWindow, windows.get(9));
+ assertEquals(exitingAppWindow, windows.get(8));
+ assertEquals(sChildAppWindowBelow, windows.get(7));
+ assertEquals(sAppWindow, windows.get(6));
+ assertEquals(sChildAppWindowAbove, windows.get(5));
+ assertEquals(sDockedDividerWindow, windows.get(4));
+ assertEquals(sStatusBarWindow, windows.get(3));
+ assertEquals(sNavBarWindow, windows.get(2));
+ assertEquals(sImeWindow, windows.get(1));
+ assertEquals(sImeDialogWindow, windows.get(0));
+ }
+
+ @Test
+ public void testForAllWindows_WithImeTarget() throws Exception {
+ final WindowState imeAppTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "imeAppTarget");
+
+ sWm.mInputMethodTarget = imeAppTarget;
+
+ final ArrayList<WindowState> windows = new ArrayList();
+
+ // Test forward traversal.
+ sDisplayContent.forAllWindows(w -> {windows.add(w);}, false /* traverseTopToBottom */);
+
+ assertEquals(sWallpaperWindow, windows.get(0));
+ assertEquals(sChildAppWindowBelow, windows.get(1));
+ assertEquals(sAppWindow, windows.get(2));
+ assertEquals(sChildAppWindowAbove, windows.get(3));
+ assertEquals(imeAppTarget, windows.get(4));
+ assertEquals(sImeWindow, windows.get(5));
+ assertEquals(sImeDialogWindow, windows.get(6));
+ assertEquals(sDockedDividerWindow, windows.get(7));
+ assertEquals(sStatusBarWindow, windows.get(8));
+ assertEquals(sNavBarWindow, windows.get(9));
+
+ // Test backward traversal.
+ windows.clear();
+ sDisplayContent.forAllWindows(w -> {windows.add(w);}, true /* traverseTopToBottom */);
+
+ assertEquals(sWallpaperWindow, windows.get(9));
+ assertEquals(sChildAppWindowBelow, windows.get(8));
+ assertEquals(sAppWindow, windows.get(7));
+ assertEquals(sChildAppWindowAbove, windows.get(6));
+ assertEquals(imeAppTarget, windows.get(5));
+ assertEquals(sImeWindow, windows.get(4));
+ assertEquals(sImeDialogWindow, windows.get(3));
+ assertEquals(sDockedDividerWindow, windows.get(2));
+ assertEquals(sStatusBarWindow, windows.get(1));
+ assertEquals(sNavBarWindow, windows.get(0));
+
+ // Clean-up
+ sWm.mInputMethodTarget = null;
+ imeAppTarget.removeImmediately();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
index 5a035d6..c8650bf 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -25,14 +24,9 @@
import android.support.test.runner.AndroidJUnit4;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
/**
* Tests for the {@link WindowLayersController} class.
@@ -45,43 +39,6 @@
@RunWith(AndroidJUnit4.class)
public class WindowLayersControllerTests extends WindowTestsBase {
- private static boolean sOneTimeSetupDone = false;
- private static WindowLayersController sLayersController;
- private static DisplayContent sDisplayContent;
- private static WindowState sImeWindow;
- private static WindowState sImeDialogWindow;
- private static WindowState sStatusBarWindow;
- private static WindowState sDockedDividerWindow;
- private static WindowState sNavBarWindow;
- private static WindowState sAppWindow;
- private static WindowState sChildAppWindow;
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- if (sOneTimeSetupDone) {
- return;
- }
- sOneTimeSetupDone = true;
- sLayersController = new WindowLayersController(sWm);
- sDisplayContent =
- new DisplayContent(mDisplay, sWm, sLayersController, new WallpaperController(sWm));
- final WindowState wallpaperWindow =
- createWindow(null, TYPE_WALLPAPER, sDisplayContent, "wallpaperWindow");
- sImeWindow = createWindow(null, TYPE_INPUT_METHOD, sDisplayContent, "sImeWindow");
- sImeDialogWindow =
- createWindow(null, TYPE_INPUT_METHOD_DIALOG, sDisplayContent, "sImeDialogWindow");
- sStatusBarWindow = createWindow(null, TYPE_STATUS_BAR, sDisplayContent, "sStatusBarWindow");
- sNavBarWindow =
- createWindow(null, TYPE_NAVIGATION_BAR, sStatusBarWindow.mToken, "sNavBarWindow");
- sDockedDividerWindow =
- createWindow(null, TYPE_DOCK_DIVIDER, sDisplayContent, "sDockedDividerWindow");
- sAppWindow = createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "sAppWindow");
- sChildAppWindow = createWindow(sAppWindow,
- TYPE_APPLICATION_ATTACHED_DIALOG, sAppWindow.mToken, "sChildAppWindow");
- }
-
@Test
public void testAssignWindowLayers_ForImeWithNoTarget() throws Exception {
sWm.mInputMethodTarget = null;
@@ -90,7 +47,7 @@
// The Ime has an higher base layer than app windows and lower base layer than system
// windows, so it should be above app windows and below system windows if there isn't an IME
// target.
- assertWindowLayerGreaterThan(sImeWindow, sChildAppWindow);
+ assertWindowLayerGreaterThan(sImeWindow, sChildAppWindowAbove);
assertWindowLayerGreaterThan(sImeWindow, sAppWindow);
assertWindowLayerGreaterThan(sImeWindow, sDockedDividerWindow);
assertWindowLayerGreaterThan(sNavBarWindow, sImeWindow);
@@ -110,7 +67,63 @@
// Ime should be above all app windows and below system windows if it is targeting an app
// window.
assertWindowLayerGreaterThan(sImeWindow, imeAppTarget);
- assertWindowLayerGreaterThan(sImeWindow, sChildAppWindow);
+ assertWindowLayerGreaterThan(sImeWindow, sChildAppWindowAbove);
+ assertWindowLayerGreaterThan(sImeWindow, sAppWindow);
+ assertWindowLayerGreaterThan(sImeWindow, sDockedDividerWindow);
+ assertWindowLayerGreaterThan(sNavBarWindow, sImeWindow);
+ assertWindowLayerGreaterThan(sStatusBarWindow, sImeWindow);
+
+ // And, IME dialogs should always have an higher layer than the IME.
+ assertWindowLayerGreaterThan(sImeDialogWindow, sImeWindow);
+ }
+
+ @Test
+ public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() throws Exception {
+ final WindowState imeAppTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "imeAppTarget");
+ final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget,
+ TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken,
+ "imeAppTargetChildAboveWindow");
+ final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget,
+ TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
+ "imeAppTargetChildBelowWindow");
+
+ sWm.mInputMethodTarget = imeAppTarget;
+ sLayersController.assignWindowLayers(sDisplayContent);
+
+ // Ime should be above all app windows except for child windows that are z-ordered above it
+ // and below system windows if it is targeting an app window.
+ assertWindowLayerGreaterThan(sImeWindow, imeAppTarget);
+ assertWindowLayerGreaterThan(imeAppTargetChildAboveWindow, sImeWindow);
+ assertWindowLayerGreaterThan(sImeWindow, imeAppTargetChildBelowWindow);
+ assertWindowLayerGreaterThan(sImeWindow, sChildAppWindowAbove);
+ assertWindowLayerGreaterThan(sImeWindow, sAppWindow);
+ assertWindowLayerGreaterThan(sImeWindow, sDockedDividerWindow);
+ assertWindowLayerGreaterThan(sNavBarWindow, sImeWindow);
+ assertWindowLayerGreaterThan(sStatusBarWindow, sImeWindow);
+
+ // And, IME dialogs should always have an higher layer than the IME.
+ assertWindowLayerGreaterThan(sImeDialogWindow, sImeWindow);
+ }
+
+ @Test
+ public void testAssignWindowLayers_ForImeWithAppTargetAndAppAbove() throws Exception {
+ final WindowState appBelowImeTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "appBelowImeTarget");
+ final WindowState imeAppTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "imeAppTarget");
+ final WindowState appAboveImeTarget =
+ createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "appAboveImeTarget");
+
+ sWm.mInputMethodTarget = imeAppTarget;
+ sLayersController.assignWindowLayers(sDisplayContent);
+
+ // Ime should be above all app windows except for non-fullscreen app window above it and
+ // below system windows if it is targeting an app window.
+ assertWindowLayerGreaterThan(sImeWindow, imeAppTarget);
+ assertWindowLayerGreaterThan(sImeWindow, appBelowImeTarget);
+ assertWindowLayerGreaterThan(appAboveImeTarget, sImeWindow);
+ assertWindowLayerGreaterThan(sImeWindow, sChildAppWindowAbove);
assertWindowLayerGreaterThan(sImeWindow, sAppWindow);
assertWindowLayerGreaterThan(sImeWindow, sDockedDividerWindow);
assertWindowLayerGreaterThan(sNavBarWindow, sImeWindow);
@@ -131,7 +144,7 @@
// The IME target base layer is higher than all window except for the nav bar window, so the
// IME should be above all windows except for the nav bar.
assertWindowLayerGreaterThan(sImeWindow, imeSystemOverlayTarget);
- assertWindowLayerGreaterThan(sImeWindow, sChildAppWindow);
+ assertWindowLayerGreaterThan(sImeWindow, sChildAppWindowAbove);
assertWindowLayerGreaterThan(sImeWindow, sAppWindow);
assertWindowLayerGreaterThan(sImeWindow, sDockedDividerWindow);
assertWindowLayerGreaterThan(sImeWindow, sStatusBarWindow);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 9681bd2..e301b19 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -22,7 +22,6 @@
import android.content.Context;
import android.os.IBinder;
import android.support.test.InstrumentationRegistry;
-import android.view.Display;
import android.view.IWindow;
import android.view.WindowManager;
@@ -31,6 +30,15 @@
import static android.content.res.Configuration.EMPTY;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static org.mockito.Mockito.mock;
/**
@@ -40,15 +48,49 @@
static WindowManagerService sWm = null;
private final IWindow mIWindow = new TestIWindow();
private final Session mMockSession = mock(Session.class);
- Display mDisplay;
private static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
private static int sNextTaskId = 0;
+ private static boolean sOneTimeSetupDone = false;
+ protected static DisplayContent sDisplayContent;
+ protected static WindowLayersController sLayersController;
+ protected static WindowState sWallpaperWindow;
+ protected static WindowState sImeWindow;
+ protected static WindowState sImeDialogWindow;
+ protected static WindowState sStatusBarWindow;
+ protected static WindowState sDockedDividerWindow;
+ protected static WindowState sNavBarWindow;
+ protected static WindowState sAppWindow;
+ protected static WindowState sChildAppWindowAbove;
+ protected static WindowState sChildAppWindowBelow;
+
@Before
public void setUp() throws Exception {
+ if (sOneTimeSetupDone) {
+ return;
+ }
+ sOneTimeSetupDone = true;
final Context context = InstrumentationRegistry.getTargetContext();
sWm = TestWindowManagerPolicy.getWindowManagerService(context);
- mDisplay = context.getDisplay();
+ sLayersController = new WindowLayersController(sWm);
+ sDisplayContent = new DisplayContent(context.getDisplay(), sWm, sLayersController,
+ new WallpaperController(sWm));
+
+ // Set-up some common windows.
+ sWallpaperWindow = createWindow(null, TYPE_WALLPAPER, sDisplayContent, "wallpaperWindow");
+ sImeWindow = createWindow(null, TYPE_INPUT_METHOD, sDisplayContent, "sImeWindow");
+ sImeDialogWindow =
+ createWindow(null, TYPE_INPUT_METHOD_DIALOG, sDisplayContent, "sImeDialogWindow");
+ sStatusBarWindow = createWindow(null, TYPE_STATUS_BAR, sDisplayContent, "sStatusBarWindow");
+ sNavBarWindow =
+ createWindow(null, TYPE_NAVIGATION_BAR, sStatusBarWindow.mToken, "sNavBarWindow");
+ sDockedDividerWindow =
+ createWindow(null, TYPE_DOCK_DIVIDER, sDisplayContent, "sDockedDividerWindow");
+ sAppWindow = createWindow(null, TYPE_BASE_APPLICATION, sDisplayContent, "sAppWindow");
+ sChildAppWindowAbove = createWindow(sAppWindow,
+ TYPE_APPLICATION_ATTACHED_DIALOG, sAppWindow.mToken, "sChildAppWindowAbove");
+ sChildAppWindowBelow = createWindow(sAppWindow,
+ TYPE_APPLICATION_MEDIA_OVERLAY, sAppWindow.mToken, "sChildAppWindowBelow");
}
/** Asserts that the first entry is greater than the second entry. */
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d7f96a9..a0f5217 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -73,6 +73,15 @@
KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
/**
+ * When checking if a given number is the voicemail number, if this flag is true
+ * then in addition to comparing the given number to the voicemail number, we also compare it
+ * to the mdn. If this flag is false, the given number is only compared to the voicemail number.
+ * By default this value is false.
+ */
+ public static final String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL =
+ "mdn_is_additional_voicemail_number_bool";
+
+ /**
* Flag indicating whether the Phone app should provide a "Dismiss" button on the SIM network
* unlock screen. The default value is true. If set to false, there will be *no way* to dismiss
* the SIM network unlock screen if you don't enter the correct unlock code. (One important
@@ -1148,6 +1157,7 @@
sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false);
sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false);
+ sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false);
sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true);
sDefaults.putBoolean(KEY_PREFER_2G_BOOL, true);
sDefaults.putBoolean(KEY_SHOW_APN_SETTING_CDMA_BOOL, false);
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 152b868..6f51c6e 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -29,6 +29,7 @@
import android.location.CountryDetector;
import android.net.Uri;
import android.os.SystemProperties;
+import android.os.PersistableBundle;
import android.provider.Contacts;
import android.provider.ContactsContract;
import android.telecom.PhoneAccount;
@@ -2106,7 +2107,7 @@
* number provided by the RIL and SIM card. The caller must have
* the READ_PHONE_STATE credential.
*
- * @param context a non-null {@link Context}.
+ * @param context {@link Context}.
* @param subId the subscription id of the SIM.
* @param number the number to look up.
* @return true if the number is in the list of voicemail. False
@@ -2115,25 +2116,54 @@
* @hide
*/
public static boolean isVoiceMailNumber(Context context, int subId, String number) {
- String vmNumber;
+ String vmNumber, mdn;
try {
final TelephonyManager tm;
if (context == null) {
tm = TelephonyManager.getDefault();
+ if (DBG) log("isVoiceMailNumber: default tm");
} else {
tm = TelephonyManager.from(context);
+ if (DBG) log("isVoiceMailNumber: tm from context");
}
vmNumber = tm.getVoiceMailNumber(subId);
+ mdn = tm.getLine1Number(subId);
+ if (DBG) log("isVoiceMailNumber: mdn=" + mdn + ", vmNumber=" + vmNumber
+ + ", number=" + number);
} catch (SecurityException ex) {
+ if (DBG) log("isVoiceMailNumber: SecurityExcpetion caught");
return false;
}
// Strip the separators from the number before comparing it
// to the list.
number = extractNetworkPortionAlt(number);
+ if (TextUtils.isEmpty(number)) {
+ if (DBG) log("isVoiceMailNumber: number is empty after stripping");
+ return false;
+ }
- // compare tolerates null so we need to make sure that we
- // don't return true when both are null.
- return !TextUtils.isEmpty(number) && compare(number, vmNumber);
+ // check if the carrier considers MDN to be an additional voicemail number
+ boolean compareWithMdn = false;
+ if (context != null) {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ if (configManager != null) {
+ PersistableBundle b = configManager.getConfigForSubId(subId);
+ if (b != null) {
+ compareWithMdn = b.getBoolean(CarrierConfigManager.
+ KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL);
+ if (DBG) log("isVoiceMailNumber: compareWithMdn=" + compareWithMdn);
+ }
+ }
+ }
+
+ if (compareWithMdn) {
+ if (DBG) log("isVoiceMailNumber: treating mdn as additional vm number");
+ return compare(number, vmNumber) || compare(number, mdn);
+ } else {
+ if (DBG) log("isVoiceMailNumber: returning regular compare");
+ return compare(number, vmNumber);
+ }
}
/**
diff --git a/telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl b/telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl
index 39e83c6..0da27e1 100644
--- a/telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl
+++ b/telephony/java/com/android/ims/internal/IImsVideoCallProvider.aidl
@@ -43,7 +43,7 @@
oneway interface IImsVideoCallProvider {
void setCallback(IImsVideoCallCallback callback);
- void setCamera(String cameraId);
+ void setCamera(String cameraId, int uid);
void setPreviewSurface(in Surface surface);
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index d4104bd..20c303e 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -438,7 +438,7 @@
// check to see if these are recognized numbers, and use shortcuts if we can.
if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
cw.event = EVENT_EMERGENCY_NUMBER;
- } else if (PhoneNumberUtils.isVoiceMailNumber(subId, number)) {
+ } else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
cw.event = EVENT_VOICEMAIL_NUMBER;
} else {
cw.event = EVENT_NEW_QUERY;
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 51adfe7..ff61754 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -661,9 +661,13 @@
// The IPv6 all nodes address ff02::1
private static final byte[] IPV6_ALL_NODES_ADDRESS =
{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+ private static final byte[] IPV6_ALL_ROUTERS_ADDRESS =
+ { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 };
private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+ private static final int ICMP6_ROUTER_SOLICITATION = 133;
private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+ private static final int ICMP6_NEIGHBOR_SOLICITATION = 135;
private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
private static final int ICMP6_RA_HEADER_LEN = 16;
@@ -798,6 +802,12 @@
put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS);
assertDrop(program, packet.array());
+ // Verify ICMPv6 RS to any is dropped
+ packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION);
+ assertDrop(program, packet.array());
+ put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS);
+ assertDrop(program, packet.array());
+
apfFilter.shutdown();
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 9da65a6..35cf903 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -80,7 +80,7 @@
public BridgeTypedArray(Resources resources, BridgeContext context, int len,
boolean platformFile) {
- super(resources, null, null, 0);
+ super(resources);
mBridgeResources = resources;
mContext = context;
mPlatformFile = platformFile;
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 82d41e3..69e9fcd 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -98,10 +98,22 @@
*/
public static final int OSEN = 5;
+ /**
+ * IEEE 802.11r Fast BSS Transition with PSK authentication.
+ * @hide
+ */
+ public static final int FT_PSK = 6;
+
+ /**
+ * IEEE 802.11r Fast BSS Transition with EAP authentication.
+ * @hide
+ */
+ public static final int FT_EAP = 7;
+
public static final String varName = "key_mgmt";
public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X",
- "WPA2_PSK", "OSEN" };
+ "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP" };
}
/**
diff --git a/wifi/java/android/net/wifi/aware/SubscribeConfig.java b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
index bf35445..0fe69a8 100644
--- a/wifi/java/android/net/wifi/aware/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
@@ -405,7 +405,7 @@
* single match session (corresponding to the same publish action on the
* peer) reported to the host (using the
* {@link WifiAwareDiscoverySessionCallback#onServiceDiscovered(WifiAwareManager.PeerHandle,
- * byte[], byte[])}). The options are: only report the first match and ignore the rest
+ * byte[], List)}). The options are: only report the first match and ignore the rest
* {@link SubscribeConfig#MATCH_STYLE_FIRST_ONLY} or report every single
* match {@link SubscribeConfig#MATCH_STYLE_ALL} (the default).
*
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareCharacteristics.java b/wifi/java/android/net/wifi/aware/WifiAwareCharacteristics.java
index 95d128d..092aa34 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareCharacteristics.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareCharacteristics.java
@@ -70,8 +70,8 @@
/**
* Returns the maximum length of byte array that can be used to specify a Aware match filter.
* Restricts the parameters of the
- * {@link PublishConfig.Builder#setMatchFilter(java.util.List<byte[]>)} and
- * {@link SubscribeConfig.Builder#setMatchFilter(java.util.List<byte[]>)}.
+ * {@link PublishConfig.Builder#setMatchFilter(java.util.List)} and
+ * {@link SubscribeConfig.Builder#setMatchFilter(java.util.List)}.
*
* @return A positive integer, maximum legngth of byte array for Aware discovery match filter.
*/
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareDiscoveryBaseSession.java b/wifi/java/android/net/wifi/aware/WifiAwareDiscoveryBaseSession.java
index 451d8a5..2812ad4 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareDiscoveryBaseSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareDiscoveryBaseSession.java
@@ -140,7 +140,7 @@
* Sends a message to the specified destination. Aware messages are transmitted in the context
* of a discovery session - executed subsequent to a publish/subscribe
* {@link WifiAwareDiscoverySessionCallback#onServiceDiscovered(WifiAwareManager.PeerHandle,
- * byte[], java.util.List<byte[]>)} event.
+ * byte[], java.util.List)} event.
* <p>
* Aware messages are not guaranteed delivery. Callbacks on
* {@link WifiAwareDiscoverySessionCallback} indicate message was transmitted successfully,
@@ -154,7 +154,7 @@
*
* @param peerHandle The peer's handle for the message. Must be a result of an
* {@link WifiAwareDiscoverySessionCallback#onServiceDiscovered(WifiAwareManager.PeerHandle,
- * byte[], java.util.List<byte[]>)} or
+ * byte[], java.util.List)} or
* {@link WifiAwareDiscoverySessionCallback#onMessageReceived(WifiAwareManager.PeerHandle,
* byte[])} events.
* @param messageId An arbitrary integer used by the caller to identify the message. The same
@@ -187,7 +187,7 @@
* Sends a message to the specified destination. Aware messages are transmitted in the context
* of a discovery session - executed subsequent to a publish/subscribe
* {@link WifiAwareDiscoverySessionCallback#onServiceDiscovered(WifiAwareManager.PeerHandle,
- * byte[], java.util.List<byte[]>)} event.
+ * byte[], java.util.List)} event.
* <p>
* Aware messages are not guaranteed delivery. Callbacks on
* {@link WifiAwareDiscoverySessionCallback} indicate message was transmitted successfully,
@@ -203,7 +203,7 @@
*
* @param peerHandle The peer's handle for the message. Must be a result of an
* {@link WifiAwareDiscoverySessionCallback#onServiceDiscovered(WifiAwareManager.PeerHandle,
- * byte[], java.util.List<byte[]>)} or
+ * byte[], java.util.List)} or
* {@link WifiAwareDiscoverySessionCallback#onMessageReceived(WifiAwareManager.PeerHandle,
* byte[])} events.
* @param messageId An arbitrary integer used by the caller to identify the message. The same
@@ -220,7 +220,7 @@
/**
* Start a ranging operation with the specified peers. The peer IDs are obtained from an
* {@link WifiAwareDiscoverySessionCallback#onServiceDiscovered(WifiAwareManager.PeerHandle,
- * byte[], java.util.List<byte[]>)} or
+ * byte[], java.util.List)} or
* {@link WifiAwareDiscoverySessionCallback#onMessageReceived(WifiAwareManager.PeerHandle,
* byte[])} operation - can
* only range devices which are part of an ongoing discovery session.
@@ -266,7 +266,7 @@
*
* @param peerHandle The peer's handle obtained through
* {@link WifiAwareDiscoverySessionCallback#onServiceDiscovered(WifiAwareManager.PeerHandle,
- * byte[], java.util.List<byte[]>)} or
+ * byte[], java.util.List)} or
* {@link WifiAwareDiscoverySessionCallback#onMessageReceived(WifiAwareManager.PeerHandle,
* byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
* from only that peer. A RESPONDER may specified a null - indicating that