Merge "Apply Spinner dropDownSelector value from XML"
diff --git a/api/current.txt b/api/current.txt
index 6c49041..47d3207 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -334,6 +334,7 @@
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
field public static final int canControlMagnification = 16844040; // 0x1010508
+ field public static final int canPerformGestures = 16844046; // 0x101050e
field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2610,6 +2611,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2649,6 +2651,12 @@
field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
}
+ public static abstract class AccessibilityService.GestureResultCallback {
+ ctor public AccessibilityService.GestureResultCallback();
+ method public void onCancelled(android.accessibilityservice.GestureDescription);
+ method public void onCompleted(android.accessibilityservice.GestureDescription);
+ }
+
public static final class AccessibilityService.MagnificationController {
method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
@@ -2681,6 +2689,7 @@
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
+ field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -2707,6 +2716,30 @@
field public java.lang.String[] packageNames;
}
+ public final class GestureDescription {
+ method public static android.accessibilityservice.GestureDescription createClick(int, int);
+ method public static android.accessibilityservice.GestureDescription createLongClick(int, int);
+ method public static android.accessibilityservice.GestureDescription createPinch(int, int, int, int, float, long);
+ method public static android.accessibilityservice.GestureDescription createSwipe(int, int, int, int, long);
+ method public android.accessibilityservice.GestureDescription.StrokeDescription getStroke(int);
+ method public int getStrokeCount();
+ field public static final long MAX_GESTURE_DURATION_MS = 60000L; // 0xea60L
+ field public static final int MAX_STROKE_COUNT = 10; // 0xa
+ }
+
+ public static class GestureDescription.Builder {
+ ctor public GestureDescription.Builder();
+ method public android.accessibilityservice.GestureDescription.Builder addStroke(android.accessibilityservice.GestureDescription.StrokeDescription);
+ method public android.accessibilityservice.GestureDescription build();
+ }
+
+ public static class GestureDescription.StrokeDescription {
+ ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
+ method public long getDuration();
+ method public android.graphics.Path getPath();
+ method public long getStartTime();
+ }
+
}
package android.accounts {
@@ -5760,6 +5793,7 @@
method public boolean getCameraDisabled(android.content.ComponentName);
method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
+ method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
method public java.lang.String getDeviceOwnerLockScreenInfo();
@@ -5824,6 +5858,7 @@
method public void setCameraDisabled(android.content.ComponentName, boolean);
method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+ method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6133,6 +6168,7 @@
method public int describeContents();
method public int getBackoffPolicy();
method public android.os.PersistableBundle getExtras();
+ method public long getFlexMillis();
method public int getId();
method public long getInitialBackoffMillis();
method public long getIntervalMillis();
@@ -6150,6 +6186,8 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final long MIN_FLEX_MILLIS = 300000L; // 0x493e0L
+ field public static final long MIN_PERIOD_MILLIS = 3600000L; // 0x36ee80L
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6163,6 +6201,7 @@
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
+ method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -6223,6 +6262,8 @@
public static class NetworkStats.Bucket {
ctor public NetworkStats.Bucket();
method public long getEndTimeStamp();
+ method public int getMetering();
+ method public int getRoaming();
method public long getRxBytes();
method public long getRxPackets();
method public long getStartTimeStamp();
@@ -6230,6 +6271,12 @@
method public long getTxBytes();
method public long getTxPackets();
method public int getUid();
+ field public static final int METERING_ALL = -1; // 0xffffffff
+ field public static final int METERING_DEFAULT = 1; // 0x1
+ field public static final int METERING_METERED = 2; // 0x2
+ field public static final int ROAMING_ALL = -1; // 0xffffffff
+ field public static final int ROAMING_DEFAULT = 1; // 0x1
+ field public static final int ROAMING_ROAMING = 2; // 0x2
field public static final int STATE_ALL = -1; // 0xffffffff
field public static final int STATE_DEFAULT = 1; // 0x1
field public static final int STATE_FOREGROUND = 2; // 0x2
@@ -9443,8 +9490,6 @@
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public abstract byte[] getEphemeralCookie();
- method public abstract int getEphemeralCookieMaxSizeBytes();
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
@@ -9478,7 +9523,6 @@
method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract boolean hasSystemFeature(java.lang.String);
- method public abstract boolean isEphemeralApplication();
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -9496,7 +9540,6 @@
method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public abstract boolean setEphemeralCookie(byte[]);
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
method public abstract void verifyPendingInstall(int, int);
field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0
@@ -12743,6 +12786,7 @@
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
field public static final int PADDING_MODE_NEST = 0; // 0x0
field public static final int PADDING_MODE_STACK = 1; // 0x1
+ field public static final int UNDEFINED_INSET = -2147483648; // 0x80000000
}
public class LevelListDrawable extends android.graphics.drawable.DrawableContainer {
@@ -22538,7 +22582,16 @@
public class MtpEvent {
ctor public MtpEvent();
+ method public int getDevicePropCode();
method public int getEventCode();
+ method public int getObjectFormatCode();
+ method public int getObjectHandle();
+ method public int getObjectPropCode();
+ method public int getParameter1();
+ method public int getParameter2();
+ method public int getParameter3();
+ method public int getStorageId();
+ method public int getTransactionId();
}
public final class MtpObjectInfo {
@@ -27189,7 +27242,7 @@
ctor public GLException(int, java.lang.String);
}
- public class GLSurfaceView extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback {
+ public class GLSurfaceView extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback2 {
ctor public GLSurfaceView(android.content.Context);
ctor public GLSurfaceView(android.content.Context, android.util.AttributeSet);
method public int getDebugFlags();
@@ -27213,6 +27266,7 @@
method public void surfaceChanged(android.view.SurfaceHolder, int, int, int);
method public void surfaceCreated(android.view.SurfaceHolder);
method public void surfaceDestroyed(android.view.SurfaceHolder);
+ method public void surfaceRedrawNeeded(android.view.SurfaceHolder);
field public static final int DEBUG_CHECK_GL_ERROR = 1; // 0x1
field public static final int DEBUG_LOG_GL_CALLS = 2; // 0x2
field public static final int RENDERMODE_CONTINUOUSLY = 1; // 0x1
@@ -31327,6 +31381,7 @@
field public static final java.lang.String AUTO_TIME = "auto_time";
field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone";
field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on";
+ field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATA_ROAMING = "data_roaming";
field public static final java.lang.String DEBUG_APP = "debug_app";
@@ -33569,6 +33624,7 @@
field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
+ field public static final int REASON_TOPIC_BANNED = 14; // 0xe
field public static final int REASON_USER_STOPPED = 6; // 0x6
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -36793,8 +36849,6 @@
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public byte[] getEphemeralCookie();
- method public int getEphemeralCookieMaxSizeBytes();
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
@@ -36827,7 +36881,6 @@
method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public boolean hasSystemFeature(java.lang.String);
- method public boolean isEphemeralApplication();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -36845,7 +36898,6 @@
method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method public void setApplicationEnabledSetting(java.lang.String, int, int);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public boolean setEphemeralCookie(byte[]);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
method public void verifyPendingInstall(int, int);
}
@@ -39076,7 +39128,9 @@
method public static android.util.LocaleList getEmptyLocaleList();
method public java.util.Locale getFirstMatch(java.lang.String[]);
method public java.util.Locale getPrimary();
+ method public int indexOf(java.util.Locale);
method public boolean isEmpty();
+ method public static void setDefault(android.util.LocaleList);
method public int size();
method public java.lang.String toLanguageTags();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 3ede80a..dbbc04b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -214,6 +214,7 @@
field public static final java.lang.String STATUS_BAR = "android.permission.STATUS_BAR";
field public static final java.lang.String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
field public static final java.lang.String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
+ field public static final java.lang.String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
field public static final java.lang.String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
field public static final java.lang.String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
field public static final java.lang.String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT";
@@ -428,6 +429,7 @@
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
field public static final int canControlMagnification = 16844040; // 0x1010508
+ field public static final int canPerformGestures = 16844046; // 0x101050e
field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2711,6 +2713,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2750,6 +2753,12 @@
field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
}
+ public static abstract class AccessibilityService.GestureResultCallback {
+ ctor public AccessibilityService.GestureResultCallback();
+ method public void onCancelled(android.accessibilityservice.GestureDescription);
+ method public void onCompleted(android.accessibilityservice.GestureDescription);
+ }
+
public static final class AccessibilityService.MagnificationController {
method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
@@ -2782,6 +2791,7 @@
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
+ field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -2808,6 +2818,30 @@
field public java.lang.String[] packageNames;
}
+ public final class GestureDescription {
+ method public static android.accessibilityservice.GestureDescription createClick(int, int);
+ method public static android.accessibilityservice.GestureDescription createLongClick(int, int);
+ method public static android.accessibilityservice.GestureDescription createPinch(int, int, int, int, float, long);
+ method public static android.accessibilityservice.GestureDescription createSwipe(int, int, int, int, long);
+ method public android.accessibilityservice.GestureDescription.StrokeDescription getStroke(int);
+ method public int getStrokeCount();
+ field public static final long MAX_GESTURE_DURATION_MS = 60000L; // 0xea60L
+ field public static final int MAX_STROKE_COUNT = 10; // 0xa
+ }
+
+ public static class GestureDescription.Builder {
+ ctor public GestureDescription.Builder();
+ method public android.accessibilityservice.GestureDescription.Builder addStroke(android.accessibilityservice.GestureDescription.StrokeDescription);
+ method public android.accessibilityservice.GestureDescription build();
+ }
+
+ public static class GestureDescription.StrokeDescription {
+ ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
+ method public long getDuration();
+ method public android.graphics.Path getPath();
+ method public long getStartTime();
+ }
+
}
package android.accounts {
@@ -5885,6 +5919,7 @@
method public boolean getCameraDisabled(android.content.ComponentName);
method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
+ method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
method public deprecated java.lang.String getDeviceInitializerApp();
@@ -5958,6 +5993,7 @@
method public void setCameraDisabled(android.content.ComponentName, boolean);
method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+ method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6347,6 +6383,7 @@
method public int describeContents();
method public int getBackoffPolicy();
method public android.os.PersistableBundle getExtras();
+ method public long getFlexMillis();
method public int getId();
method public long getInitialBackoffMillis();
method public long getIntervalMillis();
@@ -6364,6 +6401,8 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final long MIN_FLEX_MILLIS = 300000L; // 0x493e0L
+ field public static final long MIN_PERIOD_MILLIS = 3600000L; // 0x36ee80L
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6377,6 +6416,7 @@
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
+ method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -6437,6 +6477,8 @@
public static class NetworkStats.Bucket {
ctor public NetworkStats.Bucket();
method public long getEndTimeStamp();
+ method public int getMetering();
+ method public int getRoaming();
method public long getRxBytes();
method public long getRxPackets();
method public long getStartTimeStamp();
@@ -6444,6 +6486,12 @@
method public long getTxBytes();
method public long getTxPackets();
method public int getUid();
+ field public static final int METERING_ALL = -1; // 0xffffffff
+ field public static final int METERING_DEFAULT = 1; // 0x1
+ field public static final int METERING_METERED = 2; // 0x2
+ field public static final int ROAMING_ALL = -1; // 0xffffffff
+ field public static final int ROAMING_DEFAULT = 1; // 0x1
+ field public static final int ROAMING_ROAMING = 2; // 0x2
field public static final int STATE_ALL = -1; // 0xffffffff
field public static final int STATE_DEFAULT = 1; // 0x1
field public static final int STATE_FOREGROUND = 2; // 0x2
@@ -9744,8 +9792,6 @@
method public abstract int getComponentEnabledSetting(android.content.ComponentName);
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public abstract byte[] getEphemeralCookie();
- method public abstract int getEphemeralCookieMaxSizeBytes();
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
@@ -9781,7 +9827,6 @@
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract boolean hasSystemFeature(java.lang.String);
- method public abstract boolean isEphemeralApplication();
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -9801,7 +9846,6 @@
method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public abstract boolean setEphemeralCookie(byte[]);
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
method public abstract void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
@@ -13097,6 +13141,7 @@
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
field public static final int PADDING_MODE_NEST = 0; // 0x0
field public static final int PADDING_MODE_STACK = 1; // 0x1
+ field public static final int UNDEFINED_INSET = -2147483648; // 0x80000000
}
public class LevelListDrawable extends android.graphics.drawable.DrawableContainer {
@@ -24084,7 +24129,16 @@
public class MtpEvent {
ctor public MtpEvent();
+ method public int getDevicePropCode();
method public int getEventCode();
+ method public int getObjectFormatCode();
+ method public int getObjectHandle();
+ method public int getObjectPropCode();
+ method public int getParameter1();
+ method public int getParameter2();
+ method public int getParameter3();
+ method public int getStorageId();
+ method public int getTransactionId();
}
public final class MtpObjectInfo {
@@ -29179,7 +29233,7 @@
ctor public GLException(int, java.lang.String);
}
- public class GLSurfaceView extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback {
+ public class GLSurfaceView extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback2 {
ctor public GLSurfaceView(android.content.Context);
ctor public GLSurfaceView(android.content.Context, android.util.AttributeSet);
method public int getDebugFlags();
@@ -29203,6 +29257,7 @@
method public void surfaceChanged(android.view.SurfaceHolder, int, int, int);
method public void surfaceCreated(android.view.SurfaceHolder);
method public void surfaceDestroyed(android.view.SurfaceHolder);
+ method public void surfaceRedrawNeeded(android.view.SurfaceHolder);
field public static final int DEBUG_CHECK_GL_ERROR = 1; // 0x1
field public static final int DEBUG_LOG_GL_CALLS = 2; // 0x2
field public static final int RENDERMODE_CONTINUOUSLY = 1; // 0x1
@@ -33462,6 +33517,7 @@
field public static final java.lang.String AUTO_TIME = "auto_time";
field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone";
field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on";
+ field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATA_ROAMING = "data_roaming";
field public static final java.lang.String DEBUG_APP = "debug_app";
@@ -35705,6 +35761,7 @@
field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
+ field public static final int REASON_TOPIC_BANNED = 14; // 0xe
field public static final int REASON_USER_STOPPED = 6; // 0x6
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -37868,6 +37925,7 @@
field public static final java.lang.String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
field public static final java.lang.String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool";
field public static final java.lang.String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool";
+ field public static final java.lang.String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
field public static final java.lang.String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";
field public static final java.lang.String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final java.lang.String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
@@ -39135,8 +39193,6 @@
method public int getComponentEnabledSetting(android.content.ComponentName);
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public byte[] getEphemeralCookie();
- method public int getEphemeralCookieMaxSizeBytes();
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
@@ -39171,7 +39227,6 @@
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public boolean hasSystemFeature(java.lang.String);
- method public boolean isEphemeralApplication();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -39191,7 +39246,6 @@
method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public void setApplicationEnabledSetting(java.lang.String, int, int);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public boolean setEphemeralCookie(byte[]);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
@@ -41424,7 +41478,9 @@
method public static android.util.LocaleList getEmptyLocaleList();
method public java.util.Locale getFirstMatch(java.lang.String[]);
method public java.util.Locale getPrimary();
+ method public int indexOf(java.util.Locale);
method public boolean isEmpty();
+ method public static void setDefault(android.util.LocaleList);
method public int size();
method public java.lang.String toLanguageTags();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 306cf73..6eba6bd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -334,6 +334,7 @@
field public static final int calendarViewShown = 16843596; // 0x101034c
field public static final int calendarViewStyle = 16843613; // 0x101035d
field public static final int canControlMagnification = 16844040; // 0x1010508
+ field public static final int canPerformGestures = 16844046; // 0x101050e
field public static final int canRequestEnhancedWebAccessibility = 16843736; // 0x10103d8
field public static final int canRequestFilterKeyEvents = 16843737; // 0x10103d9
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
@@ -2610,6 +2611,7 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2649,6 +2651,12 @@
field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
}
+ public static abstract class AccessibilityService.GestureResultCallback {
+ ctor public AccessibilityService.GestureResultCallback();
+ method public void onCancelled(android.accessibilityservice.GestureDescription);
+ method public void onCompleted(android.accessibilityservice.GestureDescription);
+ }
+
public static final class AccessibilityService.MagnificationController {
method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
method public void addListener(android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, android.os.Handler);
@@ -2681,6 +2689,7 @@
method public java.lang.String loadDescription(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10
+ field public static final int CAPABILITY_CAN_PERFORM_GESTURES = 32; // 0x20
field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
@@ -2707,6 +2716,30 @@
field public java.lang.String[] packageNames;
}
+ public final class GestureDescription {
+ method public static android.accessibilityservice.GestureDescription createClick(int, int);
+ method public static android.accessibilityservice.GestureDescription createLongClick(int, int);
+ method public static android.accessibilityservice.GestureDescription createPinch(int, int, int, int, float, long);
+ method public static android.accessibilityservice.GestureDescription createSwipe(int, int, int, int, long);
+ method public android.accessibilityservice.GestureDescription.StrokeDescription getStroke(int);
+ method public int getStrokeCount();
+ field public static final long MAX_GESTURE_DURATION_MS = 60000L; // 0xea60L
+ field public static final int MAX_STROKE_COUNT = 10; // 0xa
+ }
+
+ public static class GestureDescription.Builder {
+ ctor public GestureDescription.Builder();
+ method public android.accessibilityservice.GestureDescription.Builder addStroke(android.accessibilityservice.GestureDescription.StrokeDescription);
+ method public android.accessibilityservice.GestureDescription build();
+ }
+
+ public static class GestureDescription.StrokeDescription {
+ ctor public GestureDescription.StrokeDescription(android.graphics.Path, long, long);
+ method public long getDuration();
+ method public android.graphics.Path getPath();
+ method public long getStartTime();
+ }
+
}
package android.accounts {
@@ -5762,6 +5795,7 @@
method public boolean getCameraDisabled(android.content.ComponentName);
method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
method public boolean getCrossProfileCallerIdDisabled(android.content.ComponentName);
+ method public boolean getCrossProfileContactsSearchDisabled(android.content.ComponentName);
method public java.util.List<java.lang.String> getCrossProfileWidgetProviders(android.content.ComponentName);
method public int getCurrentFailedPasswordAttempts();
method public java.lang.String getDeviceOwnerLockScreenInfo();
@@ -5826,6 +5860,7 @@
method public void setCameraDisabled(android.content.ComponentName, boolean);
method public void setCertInstallerPackage(android.content.ComponentName, java.lang.String) throws java.lang.SecurityException;
method public void setCrossProfileCallerIdDisabled(android.content.ComponentName, boolean);
+ method public void setCrossProfileContactsSearchDisabled(android.content.ComponentName, boolean);
method public boolean setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.String);
method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
@@ -6135,6 +6170,7 @@
method public int describeContents();
method public int getBackoffPolicy();
method public android.os.PersistableBundle getExtras();
+ method public long getFlexMillis();
method public int getId();
method public long getInitialBackoffMillis();
method public long getIntervalMillis();
@@ -6152,6 +6188,8 @@
field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+ field public static final long MIN_FLEX_MILLIS = 300000L; // 0x493e0L
+ field public static final long MIN_PERIOD_MILLIS = 3600000L; // 0x36ee80L
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6165,6 +6203,7 @@
method public android.app.job.JobInfo.Builder setMinimumLatency(long);
method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
method public android.app.job.JobInfo.Builder setPeriodic(long);
+ method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -6225,6 +6264,8 @@
public static class NetworkStats.Bucket {
ctor public NetworkStats.Bucket();
method public long getEndTimeStamp();
+ method public int getMetering();
+ method public int getRoaming();
method public long getRxBytes();
method public long getRxPackets();
method public long getStartTimeStamp();
@@ -6232,6 +6273,12 @@
method public long getTxBytes();
method public long getTxPackets();
method public int getUid();
+ field public static final int METERING_ALL = -1; // 0xffffffff
+ field public static final int METERING_DEFAULT = 1; // 0x1
+ field public static final int METERING_METERED = 2; // 0x2
+ field public static final int ROAMING_ALL = -1; // 0xffffffff
+ field public static final int ROAMING_DEFAULT = 1; // 0x1
+ field public static final int ROAMING_ROAMING = 2; // 0x2
field public static final int STATE_ALL = -1; // 0xffffffff
field public static final int STATE_DEFAULT = 1; // 0x1
field public static final int STATE_FOREGROUND = 2; // 0x2
@@ -9451,8 +9498,6 @@
method public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public abstract android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public abstract byte[] getEphemeralCookie();
- method public abstract int getEphemeralCookieMaxSizeBytes();
method public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public abstract java.lang.String getInstallerPackageName(java.lang.String);
@@ -9486,7 +9531,6 @@
method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public abstract boolean hasSystemFeature(java.lang.String);
- method public abstract boolean isEphemeralApplication();
method public abstract boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public abstract boolean isSafeMode();
method public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -9504,7 +9548,6 @@
method public abstract android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method public abstract void setApplicationEnabledSetting(java.lang.String, int, int);
method public abstract void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public abstract boolean setEphemeralCookie(byte[]);
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
method public abstract void verifyPendingInstall(int, int);
field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0
@@ -12751,6 +12794,7 @@
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
field public static final int PADDING_MODE_NEST = 0; // 0x0
field public static final int PADDING_MODE_STACK = 1; // 0x1
+ field public static final int UNDEFINED_INSET = -2147483648; // 0x80000000
}
public class LevelListDrawable extends android.graphics.drawable.DrawableContainer {
@@ -22546,7 +22590,16 @@
public class MtpEvent {
ctor public MtpEvent();
+ method public int getDevicePropCode();
method public int getEventCode();
+ method public int getObjectFormatCode();
+ method public int getObjectHandle();
+ method public int getObjectPropCode();
+ method public int getParameter1();
+ method public int getParameter2();
+ method public int getParameter3();
+ method public int getStorageId();
+ method public int getTransactionId();
}
public final class MtpObjectInfo {
@@ -27197,7 +27250,7 @@
ctor public GLException(int, java.lang.String);
}
- public class GLSurfaceView extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback {
+ public class GLSurfaceView extends android.view.SurfaceView implements android.view.SurfaceHolder.Callback2 {
ctor public GLSurfaceView(android.content.Context);
ctor public GLSurfaceView(android.content.Context, android.util.AttributeSet);
method public int getDebugFlags();
@@ -27221,6 +27274,7 @@
method public void surfaceChanged(android.view.SurfaceHolder, int, int, int);
method public void surfaceCreated(android.view.SurfaceHolder);
method public void surfaceDestroyed(android.view.SurfaceHolder);
+ method public void surfaceRedrawNeeded(android.view.SurfaceHolder);
field public static final int DEBUG_CHECK_GL_ERROR = 1; // 0x1
field public static final int DEBUG_LOG_GL_CALLS = 2; // 0x2
field public static final int RENDERMODE_CONTINUOUSLY = 1; // 0x1
@@ -31339,6 +31393,7 @@
field public static final java.lang.String AUTO_TIME = "auto_time";
field public static final java.lang.String AUTO_TIME_ZONE = "auto_time_zone";
field public static final java.lang.String BLUETOOTH_ON = "bluetooth_on";
+ field public static final java.lang.String CONTACT_METADATA_SYNC = "contact_metadata_sync";
field public static final android.net.Uri CONTENT_URI;
field public static final java.lang.String DATA_ROAMING = "data_roaming";
field public static final java.lang.String DEBUG_APP = "debug_app";
@@ -33583,6 +33638,7 @@
field public static final int REASON_LISTENER_CANCEL_ALL = 11; // 0xb
field public static final int REASON_PACKAGE_BANNED = 7; // 0x7
field public static final int REASON_PACKAGE_CHANGED = 5; // 0x5
+ field public static final int REASON_TOPIC_BANNED = 14; // 0xe
field public static final int REASON_USER_STOPPED = 6; // 0x6
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
}
@@ -36809,8 +36865,6 @@
method public android.graphics.drawable.Drawable getDefaultActivityIcon();
method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
- method public byte[] getEphemeralCookie();
- method public int getEphemeralCookieMaxSizeBytes();
method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
method public java.lang.String getInstallerPackageName(java.lang.String);
@@ -36843,7 +36897,6 @@
method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
method public boolean hasSystemFeature(java.lang.String);
- method public boolean isEphemeralApplication();
method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
method public boolean isSafeMode();
method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
@@ -36861,7 +36914,6 @@
method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
method public void setApplicationEnabledSetting(java.lang.String, int, int);
method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
- method public boolean setEphemeralCookie(byte[]);
method public void setInstallerPackageName(java.lang.String, java.lang.String);
method public void verifyPendingInstall(int, int);
}
@@ -39092,7 +39144,9 @@
method public static android.util.LocaleList getEmptyLocaleList();
method public java.util.Locale getFirstMatch(java.lang.String[]);
method public java.util.Locale getPrimary();
+ method public int indexOf(java.util.Locale);
method public boolean isEmpty();
+ method public static void setDefault(android.util.LocaleList);
method public int size();
method public java.lang.String toLanguageTags();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 468c145..3293c26 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,11 +16,13 @@
package android.accessibilityservice;
+import android.accessibilityservice.GestureDescription.MotionEventGenerator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.graphics.Region;
import android.os.Handler;
import android.os.IBinder;
@@ -29,8 +31,11 @@
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityEvent;
@@ -41,10 +46,7 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
-import java.util.ArrayList;
import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
/**
* An accessibility service runs in the background and receives callbacks by the system
@@ -376,6 +378,7 @@
public boolean onKeyEvent(KeyEvent event);
public void onMagnificationChanged(@NonNull Region region,
float scale, float centerX, float centerY);
+ public void onPerformGestureResult(int sequence, boolean completedSuccessfully);
}
private int mConnectionId;
@@ -388,6 +391,12 @@
private MagnificationController mMagnificationController;
+ private int mGestureStatusCallbackSequence;
+
+ private SparseArray<GestureResultCallbackInfo> mGestureStatusCallbackInfos;
+
+ private final Object mLock = new Object();
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -551,6 +560,88 @@
return mMagnificationController;
}
+ /**
+ * Dispatch a gesture to the touch screen. Any gestures currently in progress, whether from
+ * the user, this service, or another service, will be cancelled.
+ * <p>
+ * <strong>Note:</strong> In order to dispatch gestures, your service
+ * must declare the capability by setting the
+ * {@link android.R.styleable#AccessibilityService_canPerformGestures}
+ * property in its meta-data. For more information, see
+ * {@link #SERVICE_META_DATA}.
+ *
+ * @param gesture The gesture to dispatch
+ * @param callback The object to call back when the status of the gesture is known. If
+ * {@code null}, no status is reported.
+ * @param handler The handler on which to call back the {@code callback} object. If
+ * {@code null}, the object is called back on the service's main thread.
+ *
+ * @return {@code true} if the gesture is dispatched, {@code false} if not.
+ */
+ public final boolean dispatchGesture(@NonNull GestureDescription gesture,
+ @Nullable GestureResultCallback callback,
+ @Nullable Handler handler) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mConnectionId);
+ if (connection == null) {
+ return false;
+ }
+ List<MotionEvent> events = MotionEventGenerator.getMotionEventsFromGestureDescription(
+ gesture, 100);
+ try {
+ synchronized (mLock) {
+ connection.sendMotionEvents(++mGestureStatusCallbackSequence,
+ new ParceledListSlice<>(events));
+ if (callback != null) {
+ if (mGestureStatusCallbackInfos == null) {
+ mGestureStatusCallbackInfos = new SparseArray<>();
+ }
+ GestureResultCallbackInfo callbackInfo = new GestureResultCallbackInfo(gesture,
+ callback, handler);
+ mGestureStatusCallbackInfos.put(mGestureStatusCallbackSequence, callbackInfo);
+ }
+ }
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ return true;
+ }
+
+ void onPerformGestureResult(int sequence, final boolean completedSuccessfully) {
+ if (mGestureStatusCallbackInfos == null) {
+ return;
+ }
+ GestureResultCallbackInfo callbackInfo;
+ synchronized (mLock) {
+ callbackInfo = mGestureStatusCallbackInfos.get(sequence);
+ }
+ final GestureResultCallbackInfo finalCallbackInfo = callbackInfo;
+ if ((callbackInfo != null) && (callbackInfo.gestureDescription != null)
+ && (callbackInfo.callback != null)) {
+ if (callbackInfo.handler != null) {
+ callbackInfo.handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (completedSuccessfully) {
+ finalCallbackInfo.callback
+ .onCompleted(finalCallbackInfo.gestureDescription);
+ } else {
+ finalCallbackInfo.callback
+ .onCancelled(finalCallbackInfo.gestureDescription);
+ }
+ }
+ });
+ return;
+ }
+ if (completedSuccessfully) {
+ callbackInfo.callback.onCompleted(callbackInfo.gestureDescription);
+ } else {
+ callbackInfo.callback.onCancelled(callbackInfo.gestureDescription);
+ }
+ }
+ }
+
private void onMagnificationChanged(@NonNull Region region, float scale,
float centerX, float centerY) {
if (mMagnificationController != null) {
@@ -1082,6 +1173,11 @@
float scale, float centerX, float centerY) {
AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
}
+
+ @Override
+ public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
+ AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
+ }
});
}
@@ -1100,6 +1196,7 @@
private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
private static final int DO_ON_KEY_EVENT = 6;
private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
+ private static final int DO_GESTURE_COMPLETE = 8;
private final HandlerCaller mCaller;
@@ -1158,6 +1255,12 @@
mCaller.sendMessage(message);
}
+ public void onPerformGestureResult(int sequence, boolean successfully) {
+ Message message = mCaller.obtainMessageII(DO_GESTURE_COMPLETE, sequence,
+ successfully ? 1 : 0);
+ mCaller.sendMessage(message);
+ }
+
@Override
public void executeMessage(Message message) {
switch (message.what) {
@@ -1242,9 +1345,47 @@
mCallback.onMagnificationChanged(region, scale, centerX, centerY);
} return;
+ case DO_GESTURE_COMPLETE: {
+ final boolean successfully = message.arg2 == 1;
+ mCallback.onPerformGestureResult(message.arg1, successfully);
+ } return;
+
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
}
}
+
+ /**
+ * Class used to report status of dispatched gestures
+ */
+ public static abstract class GestureResultCallback {
+ /** Called when the gesture has completed successfully
+ *
+ * @param gestureDescription The description of the gesture that completed.
+ */
+ public void onCompleted(GestureDescription gestureDescription) {
+ }
+
+ /** Called when the gesture was cancelled
+ *
+ * @param gestureDescription The description of the gesture that was cancelled.
+ */
+ public void onCancelled(GestureDescription gestureDescription) {
+ }
+ }
+
+ /* Object to keep track of gesture result callbacks */
+ private static class GestureResultCallbackInfo {
+ GestureDescription gestureDescription;
+ GestureResultCallback callback;
+ Handler handler;
+
+ GestureResultCallbackInfo(GestureDescription gestureDescription,
+ GestureResultCallback callback, Handler handler) {
+ this.gestureDescription = gestureDescription;
+ this.callback = callback;
+ this.handler = handler;
+ }
+ }
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 2c98fef..079bdfc 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -110,6 +110,12 @@
*/
public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;
+ /**
+ * Capability: This accessibility service can perform gestures.
+ * @see android.R.styleable#AccessibilityService_canPerformGestures
+ */
+ public static final int CAPABILITY_CAN_PERFORM_GESTURES = 0x00000020;
+
private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
new SparseArray<CapabilityInfo>();
static {
@@ -133,6 +139,10 @@
new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
R.string.capability_title_canControlMagnification,
R.string.capability_desc_canControlMagnification));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_PERFORM_GESTURES,
+ new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
+ R.string.capability_title_canPerformGestures,
+ R.string.capability_desc_canPerformGestures));
}
/**
@@ -276,12 +286,7 @@
/**
* This flag requests from the system to filter key events. If this flag
* is set the accessibility service will receive the key events before
- * applications allowing it implement global shortcuts. Setting this flag
- * does not guarantee that this service will filter key events since only
- * one service can do so at any given time. This avoids user confusion due
- * to behavior change in case different key filtering services are enabled.
- * If there is already another key filtering service enabled, this one will
- * not receive key events.
+ * applications allowing it implement global shortcuts.
* <p>
* Services that want to set this flag have to declare this capability
* in their meta-data by setting the attribute {@link android.R.attr
@@ -516,6 +521,10 @@
.AccessibilityService_canControlMagnification, false)) {
mCapabilities |= CAPABILITY_CAN_CONTROL_MAGNIFICATION;
}
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canPerformGestures, false)) {
+ mCapabilities |= CAPABILITY_CAN_PERFORM_GESTURES;
+ }
TypedValue peekedValue = asAttributes.peekValue(
com.android.internal.R.styleable.AccessibilityService_description);
if (peekedValue != null) {
@@ -616,6 +625,8 @@
* @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
* @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
* @see #CAPABILITY_FILTER_KEY_EVENTS
+ * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
+ * @see #CAPABILITY_CAN_PERFORM_GESTURES
*/
public int getCapabilities() {
return mCapabilities;
@@ -631,6 +642,8 @@
* @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
* @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
* @see #CAPABILITY_FILTER_KEY_EVENTS
+ * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
+ * @see #CAPABILITY_CAN_PERFORM_GESTURES
*
* @hide
*/
@@ -933,6 +946,8 @@
return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
+ case CAPABILITY_CAN_PERFORM_GESTURES:
+ return "CAPABILITY_CAN_PERFORM_GESTURES";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/accessibilityservice/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java
new file mode 100644
index 0000000..14aabcf
--- /dev/null
+++ b/core/java/android/accessibilityservice/GestureDescription.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.RectF;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.MotionEvent.PointerCoords;
+import android.view.MotionEvent.PointerProperties;
+import android.view.ViewConfiguration;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Accessibility services with the
+ * {@link android.R.styleable#AccessibilityService_canPerformGestures} property can dispatch
+ * gestures. This class describes those gestures. Gestures are made up of one or more strokes.
+ * Gestures are immutable; use the {@code create} methods to get common gesture, or a
+ * {@code Builder} to create a new one.
+ * <p>
+ * Spatial dimensions throughout are in screen pixels. Time is measured in milliseconds.
+ */
+public final class GestureDescription {
+ /** Gestures may contain no more than this many strokes */
+ public static final int MAX_STROKE_COUNT = 10;
+
+ /**
+ * Upper bound on total gesture duration. Nearly all gestures will be much shorter.
+ */
+ public static final long MAX_GESTURE_DURATION_MS = 60 * 1000;
+
+ private final List<StrokeDescription> mStrokes = new ArrayList<>();
+ private final float[] mTempPos = new float[2];
+
+ /**
+ * Create a description of a click gesture
+ *
+ * @param x The x coordinate to click. Must not be negative.
+ * @param y The y coordinate to click. Must not be negative.
+ *
+ * @return A description of a click at (x, y)
+ */
+ public static GestureDescription createClick(@IntRange(from = 0) int x,
+ @IntRange(from = 0) int y) {
+ Path clickPath = new Path();
+ clickPath.moveTo(x, y);
+ clickPath.lineTo(x + 1, y);
+ return new GestureDescription(
+ new StrokeDescription(clickPath, 0, ViewConfiguration.getTapTimeout()));
+ }
+
+ /**
+ * Create a description of a long click gesture
+ *
+ * @param x The x coordinate to click. Must not be negative.
+ * @param y The y coordinate to click. Must not be negative.
+ *
+ * @return A description of a click at (x, y)
+ */
+ public static GestureDescription createLongClick(@IntRange(from = 0) int x,
+ @IntRange(from = 0) int y) {
+ Path clickPath = new Path();
+ clickPath.moveTo(x, y);
+ clickPath.lineTo(x + 1, y);
+ int longPressTime = ViewConfiguration.getLongPressTimeout();
+ return new GestureDescription(
+ new StrokeDescription(clickPath, 0, longPressTime + (longPressTime / 2)));
+ }
+
+ /**
+ * Create a description of a swipe gesture
+ *
+ * @param startX The x coordinate of the starting point. Must not be negative.
+ * @param startY The y coordinate of the starting point. Must not be negative.
+ * @param endX The x coordinate of the ending point. Must not be negative.
+ * @param endY The y coordinate of the ending point. Must not be negative.
+ * @param duration The time, in milliseconds, to complete the gesture. Must not be negative.
+ *
+ * @return A description of a swipe from ({@code startX}, {@code startY}) to
+ * ({@code endX}, {@code endY}) that takes {@code duration} milliseconds. Returns {@code null}
+ * if the path specified for the swipe is invalid.
+ */
+ public static GestureDescription createSwipe(@IntRange(from = 0) int startX,
+ @IntRange(from = 0) int startY,
+ @IntRange(from = 0) int endX,
+ @IntRange(from = 0) int endY,
+ @IntRange(from = 0, to = MAX_GESTURE_DURATION_MS) long duration) {
+ Path swipePath = new Path();
+ swipePath.moveTo(startX, startY);
+ swipePath.lineTo(endX, endY);
+ return new GestureDescription(new StrokeDescription(swipePath, 0, duration));
+ }
+
+ /**
+ * Create a description for a pinch (or zoom) gesture.
+ *
+ * @param centerX The x coordinate of the center of the pinch. Must not be negative.
+ * @param centerY The y coordinate of the center of the pinch. Must not be negative.
+ * @param startSpacing The spacing of the touch points at the beginning of the gesture. Must not
+ * be negative.
+ * @param endSpacing The spacing of the touch points at the end of the gesture. Must not be
+ * negative.
+ * @param orientation The angle, in degrees, of the gesture. 0 represents a horizontal pinch
+ * @param duration The time, in milliseconds, to complete the gesture. Must not be negative.
+ *
+ * @return A description of a pinch centered at ({code centerX}, {@code centerY}) that starts
+ * with the touch points spaced by {@code startSpacing} and ends with them spaced by
+ * {@code endSpacing} that lasts {@code duration} ms. Returns {@code null} if either path
+ * specified for the pinch is invalid.
+ */
+ public static GestureDescription createPinch(@IntRange(from = 0) int centerX,
+ @IntRange(from = 0) int centerY,
+ @IntRange(from = 0) int startSpacing,
+ @IntRange(from = 0) int endSpacing,
+ float orientation,
+ @IntRange(from = 0, to = MAX_GESTURE_DURATION_MS) long duration) {
+ if ((startSpacing < 0) || (endSpacing < 0)) {
+ throw new IllegalArgumentException("Pinch spacing cannot be negative");
+ }
+ float[] startPoint1 = new float[2];
+ float[] endPoint1 = new float[2];
+ float[] startPoint2 = new float[2];
+ float[] endPoint2 = new float[2];
+
+ /* Build points for a horizontal gesture centered at the origin */
+ startPoint1[0] = startSpacing / 2;
+ startPoint1[1] = 0;
+ endPoint1[0] = endSpacing / 2;
+ endPoint1[1] = 0;
+ startPoint2[0] = -startSpacing / 2;
+ startPoint2[1] = 0;
+ endPoint2[0] = -endSpacing / 2;
+ endPoint2[1] = 0;
+
+ /* Rotate and translate the points */
+ Matrix matrix = new Matrix();
+ matrix.setRotate(orientation);
+ matrix.postTranslate(centerX, centerY);
+ matrix.mapPoints(startPoint1);
+ matrix.mapPoints(endPoint1);
+ matrix.mapPoints(startPoint2);
+ matrix.mapPoints(endPoint2);
+
+ Path path1 = new Path();
+ path1.moveTo(startPoint1[0], startPoint1[1]);
+ path1.lineTo(endPoint1[0], endPoint1[1]);
+ Path path2 = new Path();
+ path2.moveTo(startPoint2[0], startPoint2[1]);
+ path2.lineTo(endPoint2[0], endPoint2[1]);
+
+ return new GestureDescription(Arrays.asList(
+ new StrokeDescription(path1, 0, duration),
+ new StrokeDescription(path2, 0, duration)));
+ }
+
+ private GestureDescription() {}
+
+ private GestureDescription(List<StrokeDescription> strokes) {
+ mStrokes.addAll(strokes);
+ }
+
+ private GestureDescription(StrokeDescription stroke) {
+ mStrokes.add(stroke);
+ }
+
+ /**
+ * Get the number of stroke in the gesture.
+ *
+ * @return the number of strokes in this gesture
+ */
+ public int getStrokeCount() {
+ return mStrokes.size();
+ }
+
+ /**
+ * Read a stroke from the gesture
+ *
+ * @param index the index of the stroke
+ *
+ * @return A description of the stroke.
+ */
+ public StrokeDescription getStroke(@IntRange(from = 0) int index) {
+ return mStrokes.get(index);
+ }
+
+ /**
+ * Return the smallest key point (where a path starts or ends) that is at least a specified
+ * offset
+ * @param offset the minimum start time
+ * @return The next key time that is at least the offset or -1 if one can't be found
+ */
+ private long getNextKeyPointAtLeast(long offset) {
+ long nextKeyPoint = Long.MAX_VALUE;
+ for (int i = 0; i < mStrokes.size(); i++) {
+ long thisStartTime = mStrokes.get(i).mStartTime;
+ if ((thisStartTime < nextKeyPoint) && (thisStartTime >= offset)) {
+ nextKeyPoint = thisStartTime;
+ }
+ long thisEndTime = mStrokes.get(i).mEndTime;
+ if ((thisEndTime < nextKeyPoint) && (thisEndTime >= offset)) {
+ nextKeyPoint = thisEndTime;
+ }
+ }
+ return (nextKeyPoint == Long.MAX_VALUE) ? -1L : nextKeyPoint;
+ }
+
+ /**
+ * Get the points that correspond to a particular moment in time.
+ * @param time The time of interest
+ * @param touchPoints An array to hold the current touch points. Must be preallocated to at
+ * least the number of paths in the gesture to prevent going out of bounds
+ * @return The number of points found, and thus the number of elements set in each array
+ */
+ private int getPointsForTime(long time, TouchPoint[] touchPoints) {
+ int numPointsFound = 0;
+ 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);
+ strokeDescription.getPosForTime(time, mTempPos);
+ touchPoints[numPointsFound].mX = Math.round(mTempPos[0]);
+ touchPoints[numPointsFound].mY = Math.round(mTempPos[1]);
+ numPointsFound++;
+ }
+ }
+ return numPointsFound;
+ }
+
+ // Total duration assumes that the gesture starts at 0; waiting around to start a gesture
+ // counts against total duration
+ private static long getTotalDuration(List<StrokeDescription> paths) {
+ long latestEnd = Long.MIN_VALUE;
+ for (int i = 0; i < paths.size(); i++) {
+ StrokeDescription path = paths.get(i);
+ latestEnd = Math.max(latestEnd, path.mEndTime);
+ }
+ return Math.max(latestEnd, 0);
+ }
+
+ /**
+ * Builder for a {@code GestureDescription}
+ */
+ public static class Builder {
+
+ private final List<StrokeDescription> mStrokes = new ArrayList<>();
+
+ /**
+ * Add a stroke to the gesture description. Up to {@code MAX_STROKE_COUNT} paths may be
+ * added to a gesture, and the total gesture duration (earliest path start time to latest path
+ * end time) may not exceed {@code MAX_GESTURE_DURATION_MS}.
+ *
+ * @param strokeDescription the stroke to add.
+ *
+ * @return this
+ */
+ public Builder addStroke(@NonNull StrokeDescription strokeDescription) {
+ if (mStrokes.size() >= MAX_STROKE_COUNT) {
+ throw new RuntimeException("Attempting to add too many strokes to a gesture");
+ }
+
+ mStrokes.add(strokeDescription);
+
+ if (getTotalDuration(mStrokes) > MAX_GESTURE_DURATION_MS) {
+ mStrokes.remove(strokeDescription);
+ throw new RuntimeException("Gesture would exceed maximum duration with new stroke");
+ }
+ return this;
+ }
+
+ public GestureDescription build() {
+ if (mStrokes.size() == 0) {
+ throw new RuntimeException("Gestures must have at least one stroke");
+ }
+ return new GestureDescription(mStrokes);
+ }
+ }
+
+ /**
+ * Immutable description of stroke that can be part of a gesture.
+ */
+ public static class StrokeDescription {
+ Path mPath;
+ long mStartTime;
+ long mEndTime;
+ private float mTimeToLengthConversion;
+ private PathMeasure mPathMeasure;
+
+ /**
+ * @param path The path to follow. Must have exactly one contour, and that contour must
+ * have nonzero length. The bounds of the path must not be negative.
+ * @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 not be negative.
+ */
+ public StrokeDescription(@NonNull Path path,
+ @IntRange(from = 0, to = MAX_GESTURE_DURATION_MS) long startTime,
+ @IntRange(from = 0, to = MAX_GESTURE_DURATION_MS) long duration) {
+ if (duration <= 0) {
+ throw new IllegalArgumentException("Duration must be positive");
+ }
+ if (startTime < 0) {
+ throw new IllegalArgumentException("Start time must not be negative");
+ }
+ RectF bounds = new RectF();
+ path.computeBounds(bounds, false /* unused */);
+ if ((bounds.bottom < 0) || (bounds.top < 0) || (bounds.right < 0)
+ || (bounds.left < 0)) {
+ throw new IllegalArgumentException("Path bounds must not be negative");
+ }
+ mPath = new Path(path);
+ mPathMeasure = new PathMeasure(path, false);
+ if (mPathMeasure.getLength() == 0) {
+ throw new IllegalArgumentException("Path has zero length");
+ }
+ if (mPathMeasure.nextContour()) {
+ throw new IllegalArgumentException("Path has more than one contour");
+ }
+ /*
+ * Calling nextContour has moved mPathMeasure off the first contour, which is the only
+ * one we care about. Set the path again to go back to the first contour.
+ */
+ mPathMeasure.setPath(path, false);
+ mStartTime = startTime;
+ mEndTime = startTime + duration;
+ if (duration > 0) {
+ mTimeToLengthConversion = getLength() / duration;
+ }
+ }
+
+ /**
+ * Retrieve a copy of the path for this stroke
+ *
+ * @return A copy of the path
+ */
+ public Path getPath() {
+ return new Path(mPath);
+ }
+
+ /**
+ * Get the stroke's start time
+ *
+ * @return the start time for this stroke.
+ */
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ /**
+ * Get the stroke's duration
+ *
+ * @return the duration for this stroke
+ */
+ public long getDuration() {
+ return mEndTime - mStartTime;
+ }
+
+ float getLength() {
+ return mPathMeasure.getLength();
+ }
+
+ /* Assumes hasPointForTime returns true */
+ boolean getPosForTime(long time, float[] pos) {
+ if (time == mEndTime) {
+ // Close to the end time, roundoff can be a problem
+ return mPathMeasure.getPosTan(getLength(), pos, null);
+ }
+ float length = mTimeToLengthConversion * ((float) (time - mStartTime));
+ return mPathMeasure.getPosTan(length, pos, null);
+ }
+
+ boolean hasPointForTime(long time) {
+ return ((time >= mStartTime) && (time <= mEndTime));
+ }
+ }
+
+ private static class TouchPoint {
+ int mPathIndex;
+ boolean mIsStartOfPath;
+ boolean mIsEndOfPath;
+ float mX;
+ float mY;
+
+ void copyFrom(TouchPoint other) {
+ mPathIndex = other.mPathIndex;
+ mIsStartOfPath = other.mIsStartOfPath;
+ mIsEndOfPath = other.mIsEndOfPath;
+ mX = other.mX;
+ mY = other.mY;
+ }
+ }
+
+ /**
+ * Class to convert a GestureDescription to a series of MotionEvents.
+ */
+ 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<MotionEvent> getMotionEventsFromGestureDescription(
+ GestureDescription description, int sampleTimeMs) {
+ final List<MotionEvent> motionEvents = new ArrayList<>();
+
+ // Point data at each time we generate an event for
+ final TouchPoint[] currentTouchPoints =
+ getCurrentTouchPoints(description.getStrokeCount());
+ // Point data sent in last touch event
+ int lastTouchPointSize = 0;
+ final TouchPoint[] lastTouchPoints =
+ getLastTouchPoints(description.getStrokeCount());
+
+ /* Loop through each time slice where there are touch points */
+ long timeSinceGestureStart = 0;
+ long nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart);
+ while (nextKeyPointTime >= 0) {
+ timeSinceGestureStart = (lastTouchPointSize == 0) ? nextKeyPointTime
+ : Math.min(nextKeyPointTime, timeSinceGestureStart + sampleTimeMs);
+ int currentTouchPointSize = description.getPointsForTime(timeSinceGestureStart,
+ currentTouchPoints);
+
+ appendMoveEventIfNeeded(motionEvents, lastTouchPoints, lastTouchPointSize,
+ currentTouchPoints, currentTouchPointSize, timeSinceGestureStart);
+ lastTouchPointSize = appendUpEvents(motionEvents, lastTouchPoints,
+ lastTouchPointSize, currentTouchPoints, currentTouchPointSize,
+ timeSinceGestureStart);
+ lastTouchPointSize = appendDownEvents(motionEvents, lastTouchPoints,
+ lastTouchPointSize, currentTouchPoints, currentTouchPointSize,
+ timeSinceGestureStart);
+
+ /* Move to next time slice */
+ nextKeyPointTime = description.getNextKeyPointAtLeast(timeSinceGestureStart + 1);
+ }
+ return motionEvents;
+ }
+
+ private static TouchPoint[] getCurrentTouchPoints(int requiredCapacity) {
+ if ((sCurrentTouchPoints == null) || (sCurrentTouchPoints.length < requiredCapacity)) {
+ sCurrentTouchPoints = new TouchPoint[requiredCapacity];
+ for (int i = 0; i < requiredCapacity; i++) {
+ sCurrentTouchPoints[i] = new TouchPoint();
+ }
+ }
+ 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/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 15666bf..6280542 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -42,4 +42,6 @@
void onKeyEvent(in KeyEvent event, int sequence);
void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
+
+ void onPerformGestureResult(int sequence, boolean completedSuccessfully);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 6ac50bd..a65b87b 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -16,10 +16,12 @@
package android.accessibilityservice;
-import android.os.Bundle;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.pm.ParceledListSlice;
import android.graphics.Region;
+import android.os.Bundle;
import android.view.MagnificationSpec;
+import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import android.view.accessibility.AccessibilityWindowInfo;
@@ -79,4 +81,6 @@
boolean animate);
void setMagnificationCallbackEnabled(boolean enabled);
+
+ void sendMotionEvents(int sequence, in ParceledListSlice events);
}
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index 8928e99..e993cca 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -861,22 +861,23 @@
if (mProperty != null) {
Object value = convertBack(mProperty.get(target));
kf.setValue(value);
- }
- try {
- if (mGetter == null) {
- Class targetClass = target.getClass();
- setupGetter(targetClass);
+ } else {
+ try {
if (mGetter == null) {
- // Already logged the error - just return to avoid NPE
- return;
+ Class targetClass = target.getClass();
+ setupGetter(targetClass);
+ if (mGetter == null) {
+ // Already logged the error - just return to avoid NPE
+ return;
+ }
}
+ Object value = convertBack(mGetter.invoke(target));
+ kf.setValue(value);
+ } catch (InvocationTargetException e) {
+ Log.e("PropertyValuesHolder", e.toString());
+ } catch (IllegalAccessException e) {
+ Log.e("PropertyValuesHolder", e.toString());
}
- Object value = convertBack(mGetter.invoke(target));
- kf.setValue(value);
- } catch (InvocationTargetException e) {
- Log.e("PropertyValuesHolder", e.toString());
- } catch (IllegalAccessException e) {
- Log.e("PropertyValuesHolder", e.toString());
}
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 64642ac..34527c2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2843,16 +2843,15 @@
if (keyCode == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
- // Capture the Alt-up and send focus to the ActionBar
+ } else if (event.isCtrlPressed() &&
+ event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<') {
+ // Capture the Control-< and send focus to the ActionBar
final int action = event.getAction();
if (action == KeyEvent.ACTION_DOWN) {
- if (event.hasModifiers(KeyEvent.META_ALT_ON)) {
- final ActionBar actionBar = getActionBar();
- if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
- mEatKeyUpEvent = true;
- return true;
- }
+ final ActionBar actionBar = getActionBar();
+ if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
+ mEatKeyUpEvent = true;
+ return true;
}
} else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) {
mEatKeyUpEvent = false;
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 8475840..dce2e51 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1031,6 +1031,11 @@
float scale, float centerX, float centerY) {
/* do nothing */
}
+
+ @Override
+ public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
+ /* do nothing */
+ }
});
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 08e9b1e..4de3ceb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3540,6 +3540,66 @@
}
/**
+ * Called by a profile owner of a managed profile to set whether contacts search from
+ * the managed profile will be shown in the parent profile, for incoming calls.
+ *
+ * <p>The calling device admin must be a profile owner. If it is not, a
+ * security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param disabled If true contacts search in the managed profile is not displayed.
+ */
+ public void setCrossProfileContactsSearchDisabled(@NonNull ComponentName admin,
+ boolean disabled) {
+ if (mService != null) {
+ try {
+ mService.setCrossProfileContactsSearchDisabled(admin, disabled);
+ } catch (RemoteException e) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+ }
+ }
+ }
+
+ /**
+ * Called by a profile owner of a managed profile to determine whether or not contacts search
+ * has been disabled.
+ *
+ * <p>The calling device admin must be a profile owner. If it is not, a
+ * security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ */
+ public boolean getCrossProfileContactsSearchDisabled(@NonNull ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getCrossProfileContactsSearchDisabled(admin);
+ } catch (RemoteException e) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Determine whether or not contacts search has been disabled.
+ *
+ * @param userHandle The user for whom to check the contacts search permission
+ * @hide
+ */
+ public boolean getCrossProfileContactsSearchDisabled(@NonNull UserHandle userHandle) {
+ if (mService != null) {
+ try {
+ return mService
+ .getCrossProfileContactsSearchDisabledForUser(userHandle.getIdentifier());
+ } catch (RemoteException e) {
+ Log.w(TAG, REMOTE_EXCEPTION_MESSAGE, e);
+ }
+ }
+ return false;
+ }
+
+ /**
* Start Quick Contact on the managed profile for the user, if the policy allows.
* @hide
*/
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 754cb43..d3c32c5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -202,6 +202,9 @@
void setCrossProfileCallerIdDisabled(in ComponentName who, boolean disabled);
boolean getCrossProfileCallerIdDisabled(in ComponentName who);
boolean getCrossProfileCallerIdDisabledForUser(int userId);
+ void setCrossProfileContactsSearchDisabled(in ComponentName who, boolean disabled);
+ boolean getCrossProfileContactsSearchDisabled(in ComponentName who);
+ boolean getCrossProfileContactsSearchDisabledForUser(int userId);
void startManagedQuickContact(String lookupKey, long contactId, long directoryId, in Intent originalIntent);
void setBluetoothContactSharingDisabled(in ComponentName who, boolean disabled);
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 0d9e778..b899710 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -64,6 +64,11 @@
*/
public static final int BACKOFF_POLICY_EXPONENTIAL = 1;
+ /* Minimum interval for a periodic job, in milliseconds. */
+ public static final long MIN_PERIOD_MILLIS = 60 * 60 * 1000L; // 60 minutes
+ /* Minimum flex for a periodic job, in milliseconds. */
+ public static final long MIN_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes
+
/**
* Default type of backoff.
* @hide
@@ -83,6 +88,7 @@
private final boolean isPeriodic;
private final boolean isPersisted;
private final long intervalMillis;
+ private final long flexMillis;
private final long initialBackoffMillis;
private final int backoffPolicy;
@@ -165,7 +171,17 @@
* job does not recur periodically.
*/
public long getIntervalMillis() {
- return intervalMillis;
+ return intervalMillis >= MIN_PERIOD_MILLIS ? intervalMillis : MIN_PERIOD_MILLIS;
+ }
+
+ /**
+ * Flex time for this job. Only valid if this is a periodic job.
+ */
+ public long getFlexMillis() {
+ long interval = getIntervalMillis();
+ long percentClamp = 5 * interval / 100;
+ long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, MIN_FLEX_MILLIS));
+ return clampedFlex <= interval ? clampedFlex : interval;
}
/**
@@ -216,6 +232,7 @@
isPeriodic = in.readInt() == 1;
isPersisted = in.readInt() == 1;
intervalMillis = in.readLong();
+ flexMillis = in.readLong();
initialBackoffMillis = in.readLong();
backoffPolicy = in.readInt();
hasEarlyConstraint = in.readInt() == 1;
@@ -234,6 +251,7 @@
isPeriodic = b.mIsPeriodic;
isPersisted = b.mIsPersisted;
intervalMillis = b.mIntervalMillis;
+ flexMillis = b.mFlexMillis;
initialBackoffMillis = b.mInitialBackoffMillis;
backoffPolicy = b.mBackoffPolicy;
hasEarlyConstraint = b.mHasEarlyConstraint;
@@ -258,6 +276,7 @@
out.writeInt(isPeriodic ? 1 : 0);
out.writeInt(isPersisted ? 1 : 0);
out.writeLong(intervalMillis);
+ out.writeLong(flexMillis);
out.writeLong(initialBackoffMillis);
out.writeInt(backoffPolicy);
out.writeInt(hasEarlyConstraint ? 1 : 0);
@@ -299,6 +318,7 @@
private boolean mHasEarlyConstraint;
private boolean mHasLateConstraint;
private long mIntervalMillis;
+ private long mFlexMillis;
// Back-off parameters.
private long mInitialBackoffMillis = DEFAULT_INITIAL_BACKOFF_MILLIS;
private int mBackoffPolicy = DEFAULT_BACKOFF_POLICY;
@@ -373,8 +393,21 @@
* @param intervalMillis Millisecond interval for which this job will repeat.
*/
public Builder setPeriodic(long intervalMillis) {
+ return setPeriodic(intervalMillis, intervalMillis);
+ }
+
+ /**
+ * Specify that this job should recur with the provided interval and flex. The job can
+ * execute at any time in a window of flex length at the end of the period.
+ * @param intervalMillis Millisecond interval for which this job will repeat.
+ * @param flexMillis Millisecond flex for this job. Flex is clamped to be at least
+ * {@link #MIN_FLEX_MILLIS} or 5 percent of the period, whichever is
+ * higher.
+ */
+ public Builder setPeriodic(long intervalMillis, long flexMillis) {
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
+ mFlexMillis = flexMillis;
mHasEarlyConstraint = mHasLateConstraint = true;
return this;
}
diff --git a/core/java/android/app/usage/NetworkStats.java b/core/java/android/app/usage/NetworkStats.java
index ef08eb9..5f97c9e 100644
--- a/core/java/android/app/usage/NetworkStats.java
+++ b/core/java/android/app/usage/NetworkStats.java
@@ -121,12 +121,12 @@
*/
public static class Bucket {
/**
- * Combined usage across all other states.
+ * Combined usage across all states.
*/
public static final int STATE_ALL = -1;
/**
- * Usage not accounted in any other states.
+ * Usage not accounted for in any other state.
*/
public static final int STATE_DEFAULT = 0x1;
@@ -150,8 +150,40 @@
*/
public static final int UID_TETHERING = TrafficStats.UID_TETHERING;
+ /**
+ * Combined usage across all metering states.
+ */
+ public static final int METERING_ALL = -1;
+
+ /**
+ * Usage not accounted for in any other metering state.
+ */
+ public static final int METERING_DEFAULT = 0x1;
+
+ /**
+ * Metered usage.
+ */
+ public static final int METERING_METERED = 0x2;
+
+ /**
+ * Combined usage across all roaming states.
+ */
+ public static final int ROAMING_ALL = -1;
+
+ /**
+ * Usage not accounted for in any other roaming state.
+ */
+ public static final int ROAMING_DEFAULT = 0x1;
+
+ /**
+ * Roaming usage.
+ */
+ public static final int ROAMING_ROAMING = 0x2;
+
private int mUid;
private int mState;
+ private int mMetering;
+ private int mRoaming;
private long mBeginTimeStamp;
private long mEndTimeStamp;
private long mRxBytes;
@@ -206,6 +238,30 @@
}
/**
+ * Metering state. One of the following values:<p/>
+ * <ul>
+ * <li>{@link #METERING_ALL}</li>
+ * <li>{@link #METERING_DEFAULT}</li>
+ * <li>{@link #METERING_METERED}</li>
+ * </ul>
+ */
+ public int getMetering() {
+ return mMetering;
+ }
+
+ /**
+ * Roaming state. One of the following values:<p/>
+ * <ul>
+ * <li>{@link #ROAMING_ALL}</li>
+ * <li>{@link #ROAMING_DEFAULT}</li>
+ * <li>{@link #ROAMING_ROAMING}</li>
+ * </ul>
+ */
+ public int getRoaming() {
+ return mRoaming;
+ }
+
+ /**
* Start timestamp of the bucket's time interval. Defined in terms of "Unix time", see
* {@link java.lang.System#currentTimeMillis}.
* @return Start of interval.
@@ -398,6 +454,9 @@
private void fillBucketFromSummaryEntry(Bucket bucketOut) {
bucketOut.mUid = Bucket.convertUid(mRecycledSummaryEntry.uid);
bucketOut.mState = Bucket.convertState(mRecycledSummaryEntry.set);
+ // TODO: Implement metering/roaming tracking.
+ bucketOut.mMetering = Bucket.METERING_ALL;
+ bucketOut.mRoaming = Bucket.ROAMING_ALL;
bucketOut.mBeginTimeStamp = mStartTimeStamp;
bucketOut.mEndTimeStamp = mEndTimeStamp;
bucketOut.mRxBytes = mRecycledSummaryEntry.rxBytes;
@@ -444,6 +503,8 @@
mRecycledHistoryEntry);
bucketOut.mUid = Bucket.convertUid(getUid());
bucketOut.mState = Bucket.STATE_ALL;
+ bucketOut.mMetering = Bucket.METERING_ALL;
+ bucketOut.mRoaming = Bucket.ROAMING_ALL;
bucketOut.mBeginTimeStamp = mRecycledHistoryEntry.bucketStart;
bucketOut.mEndTimeStamp = mRecycledHistoryEntry.bucketStart +
mRecycledHistoryEntry.bucketDuration;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f41928e..38abac7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3258,6 +3258,8 @@
* @see #setEphemeralCookie(byte[])
* @see #getEphemeralCookie()
* @see #getEphemeralCookieMaxSizeBytes()
+ *
+ * @hide
*/
public abstract boolean isEphemeralApplication();
@@ -3270,6 +3272,8 @@
* @see #isEphemeralApplication()
* @see #setEphemeralCookie(byte[])
* @see #getEphemeralCookie()
+ *
+ * @hide
*/
public abstract int getEphemeralCookieMaxSizeBytes();
@@ -3286,6 +3290,8 @@
* @see #isEphemeralApplication()
* @see #setEphemeralCookie(byte[])
* @see #getEphemeralCookieMaxSizeBytes()
+ *
+ * @hide
*/
public abstract @NonNull byte[] getEphemeralCookie();
@@ -3304,6 +3310,8 @@
* @see #isEphemeralApplication()
* @see #getEphemeralCookieMaxSizeBytes()
* @see #getEphemeralCookie()
+ *
+ * @hide
*/
public abstract boolean setEphemeralCookie(@NonNull byte[] cookie);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1349662..a0df610 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -463,92 +463,60 @@
p.featureGroups.toArray(pi.featureGroups);
}
}
- if ((flags&PackageManager.GET_ACTIVITIES) != 0) {
- int N = p.activities.size();
+ if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
+ final int N = p.activities.size();
if (N > 0) {
- if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.activities = new ActivityInfo[N];
- } else {
- int num = 0;
- for (int i=0; i<N; i++) {
- if (p.activities.get(i).info.enabled) num++;
- }
- pi.activities = new ActivityInfo[num];
- }
- for (int i=0, j=0; i<N; i++) {
- final Activity activity = p.activities.get(i);
- if (activity.info.enabled
- || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags,
- state, userId);
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[N];
+ for (int i = 0; i < N; i++) {
+ final Activity a = p.activities.get(i);
+ if (state.isMatch(a.info, flags)) {
+ res[num++] = generateActivityInfo(a, flags, state, userId);
}
}
+ pi.activities = ArrayUtils.trimToSize(res, num);
}
}
- if ((flags&PackageManager.GET_RECEIVERS) != 0) {
- int N = p.receivers.size();
+ if ((flags & PackageManager.GET_RECEIVERS) != 0) {
+ final int N = p.receivers.size();
if (N > 0) {
- if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.receivers = new ActivityInfo[N];
- } else {
- int num = 0;
- for (int i=0; i<N; i++) {
- if (p.receivers.get(i).info.enabled) num++;
- }
- pi.receivers = new ActivityInfo[num];
- }
- for (int i=0, j=0; i<N; i++) {
- final Activity activity = p.receivers.get(i);
- if (activity.info.enabled
- || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags,
- state, userId);
+ int num = 0;
+ final ActivityInfo[] res = new ActivityInfo[N];
+ for (int i = 0; i < N; i++) {
+ final Activity a = p.receivers.get(i);
+ if (state.isMatch(a.info, flags)) {
+ res[num++] = generateActivityInfo(a, flags, state, userId);
}
}
+ pi.receivers = ArrayUtils.trimToSize(res, num);
}
}
- if ((flags&PackageManager.GET_SERVICES) != 0) {
- int N = p.services.size();
+ if ((flags & PackageManager.GET_SERVICES) != 0) {
+ final int N = p.services.size();
if (N > 0) {
- if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.services = new ServiceInfo[N];
- } else {
- int num = 0;
- for (int i=0; i<N; i++) {
- if (p.services.get(i).info.enabled) num++;
- }
- pi.services = new ServiceInfo[num];
- }
- for (int i=0, j=0; i<N; i++) {
- final Service service = p.services.get(i);
- if (service.info.enabled
- || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.services[j++] = generateServiceInfo(p.services.get(i), flags,
- state, userId);
+ int num = 0;
+ final ServiceInfo[] res = new ServiceInfo[N];
+ for (int i = 0; i < N; i++) {
+ final Service s = p.services.get(i);
+ if (state.isMatch(s.info, flags)) {
+ res[num++] = generateServiceInfo(s, flags, state, userId);
}
}
+ pi.services = ArrayUtils.trimToSize(res, num);
}
}
- if ((flags&PackageManager.GET_PROVIDERS) != 0) {
- int N = p.providers.size();
+ if ((flags & PackageManager.GET_PROVIDERS) != 0) {
+ final int N = p.providers.size();
if (N > 0) {
- if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.providers = new ProviderInfo[N];
- } else {
- int num = 0;
- for (int i=0; i<N; i++) {
- if (p.providers.get(i).info.enabled) num++;
- }
- pi.providers = new ProviderInfo[num];
- }
- for (int i=0, j=0; i<N; i++) {
- final Provider provider = p.providers.get(i);
- if (provider.info.enabled
- || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags,
- state, userId);
+ int num = 0;
+ final ProviderInfo[] res = new ProviderInfo[N];
+ for (int i = 0; i < N; i++) {
+ final Provider pr = p.providers.get(i);
+ if (state.isMatch(pr.info, flags)) {
+ res[num++] = generateProviderInfo(pr, flags, state, userId);
}
}
+ pi.providers = ArrayUtils.trimToSize(res, num);
}
}
if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) {
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 91fdf7f..38e0044 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -17,9 +17,20 @@
package android.content.pm;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+import static android.content.pm.PackageManager.MATCH_ENCRYPTION_AWARE;
+import static android.content.pm.PackageManager.MATCH_ENCRYPTION_UNAWARE;
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import android.util.ArraySet;
+import com.android.internal.util.ArrayUtils;
+
/**
* Per-user state information about a package.
* @hide
@@ -58,12 +69,77 @@
hidden = o.hidden;
suspended = o.suspended;
lastDisableAppCaller = o.lastDisableAppCaller;
- disabledComponents = o.disabledComponents != null
- ? new ArraySet<>(o.disabledComponents) : null;
- enabledComponents = o.enabledComponents != null
- ? new ArraySet<>(o.enabledComponents) : null;
+ disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents);
+ enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
blockUninstall = o.blockUninstall;
domainVerificationStatus = o.domainVerificationStatus;
appLinkGeneration = o.appLinkGeneration;
}
+
+ /**
+ * Test if this package is installed.
+ */
+ public boolean isInstalled(int flags) {
+ return (this.installed && !this.hidden)
+ || (flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0;
+ }
+
+ /**
+ * Test if the given component is considered installed, enabled and a match
+ * for the given flags.
+ */
+ public boolean isMatch(ComponentInfo componentInfo, int flags) {
+ if (!isInstalled(flags)) return false;
+ if (!isEnabled(componentInfo, flags)) return false;
+
+ if ((flags & MATCH_SYSTEM_ONLY) != 0) {
+ if (!componentInfo.applicationInfo.isSystemApp()) {
+ return false;
+ }
+ }
+
+ final boolean matchesUnaware = ((flags & MATCH_ENCRYPTION_UNAWARE) != 0)
+ && !componentInfo.encryptionAware;
+ final boolean matchesAware = ((flags & MATCH_ENCRYPTION_AWARE) != 0)
+ && componentInfo.encryptionAware;
+ return matchesUnaware || matchesAware;
+ }
+
+ /**
+ * Test if the given component is considered enabled.
+ */
+ public boolean isEnabled(ComponentInfo componentInfo, int flags) {
+ if ((flags & MATCH_DISABLED_COMPONENTS) != 0) {
+ return true;
+ }
+
+ // First check if the overall package is disabled; if the package is
+ // enabled then fall through to check specific component
+ switch (this.enabled) {
+ case COMPONENT_ENABLED_STATE_DISABLED:
+ case COMPONENT_ENABLED_STATE_DISABLED_USER:
+ return false;
+ case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+ if ((flags & MATCH_DISABLED_UNTIL_USED_COMPONENTS) == 0) {
+ return false;
+ }
+ case COMPONENT_ENABLED_STATE_DEFAULT:
+ if (!componentInfo.applicationInfo.enabled) {
+ return false;
+ }
+ case COMPONENT_ENABLED_STATE_ENABLED:
+ break;
+ }
+
+ // Check if component has explicit state before falling through to
+ // the manifest default
+ if (ArrayUtils.contains(this.enabledComponents, componentInfo.name)) {
+ return true;
+ }
+ if (ArrayUtils.contains(this.disabledComponents, componentInfo.name)) {
+ return false;
+ }
+
+ return componentInfo.enabled;
+ }
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 515e9a2..cabc6fa 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.net.NetworkUtils;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
@@ -46,12 +45,12 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.Protocol;
-import java.net.InetAddress;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.HashMap;
-
import libcore.net.event.NetworkEventDispatcher;
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Class that answers queries about the state of network connectivity. It also
* notifies applications when network connectivity changes. Get an instance
@@ -1611,7 +1610,7 @@
// Have a provisioning app - must only let system apps (which check this app)
// turn on tethering
context.enforceCallingOrSelfPermission(
- android.Manifest.permission.CONNECTIVITY_INTERNAL, "ConnectivityService");
+ android.Manifest.permission.TETHER_PRIVILEGED, "ConnectivityService");
} else {
int uid = Binder.getCallingUid();
Settings.checkAndNoteWriteSettingsOperation(context, uid, Settings
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a006674..d4c9944 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8288,7 +8288,6 @@
/**
* Whether to enable contacts metadata syncing or not
* The value 1 - enable, 0 - disable
- * @hide
*/
public static final String CONTACT_METADATA_SYNC = "contact_metadata_sync";
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index aba82fa..035462d 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -95,6 +95,9 @@
/** Notification was canceled because it was an invisible member of a group. */
public static final int REASON_GROUP_OPTIMIZATION = 13;
+ /** Notification was canceled by the user banning the topic. */
+ public static final int REASON_TOPIC_BANNED = 14;
+
public class Adjustment {
int mImportance;
CharSequence mExplanation;
diff --git a/core/java/android/util/LocaleList.java b/core/java/android/util/LocaleList.java
index f22cde0..24883e3 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -16,6 +16,7 @@
package android.util;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
@@ -56,10 +57,21 @@
return mList.length == 0;
}
+ @IntRange(from=0)
public int size() {
return mList.length;
}
+ @IntRange(from=-1)
+ public int indexOf(Locale locale) {
+ for (int i = 0; i < mList.length; i++) {
+ if (mList[i].equals(locale)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this)
@@ -69,7 +81,7 @@
final Locale[] otherList = ((LocaleList) other).mList;
if (mList.length != otherList.length)
return false;
- for (int i = 0; i < mList.length; ++i) {
+ for (int i = 0; i < mList.length; i++) {
if (!mList[i].equals(otherList[i]))
return false;
}
@@ -79,7 +91,7 @@
@Override
public int hashCode() {
int result = 1;
- for (int i = 0; i < mList.length; ++i) {
+ for (int i = 0; i < mList.length; i++) {
result = 31 * result + mList[i].hashCode();
}
return result;
@@ -89,7 +101,7 @@
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
- for (int i = 0; i < mList.length; ++i) {
+ for (int i = 0; i < mList.length; i++) {
sb.append(mList[i]);
if (i < mList.length - 1) {
sb.append(',');
@@ -150,12 +162,12 @@
final Locale[] localeList = new Locale[list.length];
final HashSet<Locale> seenLocales = new HashSet<Locale>();
final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < list.length; ++i) {
+ for (int i = 0; i < list.length; i++) {
final Locale l = list[i];
if (l == null) {
- throw new NullPointerException();
+ throw new NullPointerException("list[" + i + "] is null");
} else if (seenLocales.contains(l)) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("list[" + i + "] is a repetition");
} else {
final Locale localeClone = (Locale) l.clone();
localeList[i] = localeClone;
@@ -171,6 +183,55 @@
}
}
+ /**
+ * Constructs a locale list, with the topLocale moved to the front if it already is
+ * in otherLocales, or added to the front if it isn't.
+ *
+ * {@hide}
+ */
+ public LocaleList(@NonNull Locale topLocale, LocaleList otherLocales) {
+ if (topLocale == null) {
+ throw new NullPointerException("topLocale is null");
+ }
+
+ final int inputLength = (otherLocales == null) ? 0 : otherLocales.mList.length;
+ int topLocaleIndex = -1;
+ for (int i = 0; i < inputLength; i++) {
+ if (topLocale.equals(otherLocales.mList[i])) {
+ topLocaleIndex = i;
+ break;
+ }
+ }
+
+ final int outputLength = inputLength + (topLocaleIndex == -1 ? 1 : 0);
+ final Locale[] localeList = new Locale[outputLength];
+ localeList[0] = (Locale) topLocale.clone();
+ if (topLocaleIndex == -1) {
+ // topLocale was not in otherLocales
+ for (int i = 0; i < inputLength; i++) {
+ localeList[i + 1] = (Locale) otherLocales.mList[i].clone();
+ }
+ } else {
+ for (int i = 0; i < topLocaleIndex; i++) {
+ localeList[i + 1] = (Locale) otherLocales.mList[i].clone();
+ }
+ for (int i = topLocaleIndex + 1; i < inputLength; i++) {
+ localeList[i] = (Locale) otherLocales.mList[i].clone();
+ }
+ }
+
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < outputLength; i++) {
+ sb.append(localeList[i].toLanguageTag());
+ if (i < outputLength - 1) {
+ sb.append(',');
+ }
+ }
+
+ mList = localeList;
+ mStringRepresentation = sb.toString();
+ }
+
public static final Parcelable.Creator<LocaleList> CREATOR
= new Parcelable.Creator<LocaleList>() {
@Override
@@ -196,7 +257,7 @@
} else {
final String[] tags = list.split(",");
final Locale[] localeArray = new Locale[tags.length];
- for (int i = 0; i < localeArray.length; ++i) {
+ for (int i = 0; i < localeArray.length; i++) {
localeArray[i] = Locale.forLanguageTag(tags[i]);
}
return new LocaleList(localeArray);
@@ -227,6 +288,7 @@
return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
}
+ @IntRange(from=0, to=1)
private static int matchScore(Locale supported, Locale desired) {
if (supported.equals(desired)) {
return 1; // return early so we don't do unnecessary computation
@@ -330,18 +392,79 @@
private final static Object sLock = new Object();
@GuardedBy("sLock")
- private static LocaleList sDefaultLocaleList;
+ private static LocaleList sLastExplicitlySetLocaleList = null;
+ @GuardedBy("sLock")
+ private static LocaleList sDefaultLocaleList = null;
+ @GuardedBy("sLock")
+ private static Locale sLastDefaultLocale = null;
- // TODO: fix this to return the default system locale list once we have that
+ /**
+ * The result is guaranteed to include the default Locale returned by Locale.getDefault(), but
+ * not necessarily at the top of the list. The default locale not being at the top of the list
+ * is an indication that the system has set the default locale to one of the user's other
+ * preferred locales, having concluded that the primary preference is not supported but a
+ * secondary preference is.
+ *
+ * Note that the default LocaleList would change if Locale.setDefault() is called. This method
+ * takes that into account by always checking the output of Locale.getDefault() and adjusting
+ * the default LocaleList if needed.
+ */
@NonNull @Size(min=1)
public static LocaleList getDefault() {
- Locale defaultLocale = Locale.getDefault();
+ final Locale defaultLocale = Locale.getDefault();
synchronized (sLock) {
- if (sDefaultLocaleList == null || sDefaultLocaleList.size() != 1
- || !defaultLocale.equals(sDefaultLocaleList.getPrimary())) {
- sDefaultLocaleList = new LocaleList(defaultLocale);
+ if (!defaultLocale.equals(sLastDefaultLocale)) {
+ sLastDefaultLocale = defaultLocale;
+ // It's either the first time someone has asked for the default locale list, or
+ // someone has called Locale.setDefault() since we last set or adjusted the default
+ // locale list. So let's adjust the locale list.
+ if (sDefaultLocaleList != null
+ && defaultLocale.equals(sDefaultLocaleList.getPrimary())) {
+ // The default Locale has changed, but it happens to be the first locale in the
+ // default locale list, so we don't need to construct a new locale list.
+ return sDefaultLocaleList;
+ }
+ sDefaultLocaleList = new LocaleList(defaultLocale, sLastExplicitlySetLocaleList);
}
+ // sDefaultLocaleList can't be null, since it can't be set to null by
+ // LocaleList.setDefault(), and if getDefault() is called before a call to
+ // setDefault(), sLastDefaultLocale would be null and the check above would set
+ // sDefaultLocaleList.
+ return sDefaultLocaleList;
}
- return sDefaultLocaleList;
+ }
+
+ /**
+ * Also sets the default locale by calling Locale.setDefault() with the first locale in the
+ * list.
+ *
+ * @throws NullPointerException if the input is <code>null</code>.
+ * @throws IllegalArgumentException if the input is empty.
+ */
+ public static void setDefault(@NonNull @Size(min=1) LocaleList locales) {
+ setDefault(locales, 0);
+ }
+
+ /**
+ * This may be used directly by system processes to set the default locale list for apps. For
+ * such uses, the default locale list would always come from the user preferences, but the
+ * default locale may have been chosen to be a locale other than the first locale in the locale
+ * list (based on the locales the app supports).
+ *
+ * {@hide}
+ */
+ public static void setDefault(@NonNull @Size(min=1) LocaleList locales, int localeIndex) {
+ if (locales == null) {
+ throw new NullPointerException("locales is null");
+ }
+ if (locales.isEmpty()) {
+ throw new IllegalArgumentException("locales is empty");
+ }
+ synchronized (sLock) {
+ sLastDefaultLocale = locales.get(localeIndex);
+ Locale.setDefault(sLastDefaultLocale);
+ sLastExplicitlySetLocaleList = locales;
+ sDefaultLocaleList = locales;
+ }
}
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index ecec258..a78b56a 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1328,4 +1328,15 @@
* @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
+
+ /**
+ * Calculates the stable insets without running a layout.
+ *
+ * @param displayRotation the current display rotation
+ * @param outInsets the insets to return
+ * @param displayWidth the current display width
+ * @param displayHeight the current display height
+ */
+ public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+ Rect outInsets);
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index ba5d6c2..6e5e591 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -201,10 +201,17 @@
}
/**
- * The default implementation performs the deletion around the current
- * selection position of the editable text.
- * @param beforeLength
- * @param afterLength
+ * The default implementation performs the deletion around the current selection position of the
+ * editable text.
+ *
+ * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the beginning of the
+ * text and the cursor, then this method does not fail but deletes all the characters in
+ * that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters in
+ * that range.
*/
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
@@ -213,7 +220,7 @@
if (content == null) return false;
beginBatchEdit();
-
+
int a = Selection.getSelectionStart(content);
int b = Selection.getSelectionEnd(content);
@@ -253,9 +260,9 @@
content.delete(b, end);
}
-
+
endBatchEdit();
-
+
return true;
}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index ff992d3..be7bc14 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -335,12 +335,15 @@
* but be careful to wait until the batch edit is over if one is
* in progress.</p>
*
- * @param beforeLength The number of characters to be deleted before the
- * current cursor position.
- * @param afterLength The number of characters to be deleted after the
- * current cursor position.
- * @return true on success, false if the input connection is no longer
- * valid.
+ * @param beforeLength The number of characters before the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the beginning of the
+ * text and the cursor, then this method does not fail but deletes all the characters in
+ * that range.
+ * @param afterLength The number of characters after the cursor to be deleted, in code unit.
+ * If this is greater than the number of existing characters between the cursor and
+ * the end of the text, then this method does not fail but deletes all the characters in
+ * that range.
+ * @return true on success, false if the input connection is no longer valid.
*/
public boolean deleteSurroundingText(int beforeLength, int afterLength);
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 9f08b21..054eafc 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -131,6 +131,8 @@
private static String TAG_WEBVIEW_PROVIDER = "webviewprovider";
private static String TAG_PACKAGE_NAME = "packageName";
private static String TAG_DESCRIPTION = "description";
+ // Whether or not the provider must be explicitly chosen by the user to be used.
+ private static String TAG_AVAILABILITY = "availableByDefault";
private static String TAG_SIGNATURE = "signature";
/**
@@ -180,8 +182,12 @@
throw new MissingWebViewPackageException(
"WebView provider in framework resources missing description");
}
+ String availableByDefault = parser.getAttributeValue(null, TAG_AVAILABILITY);
+ if (availableByDefault == null) {
+ availableByDefault = "false";
+ }
webViewProviders.add(
- new WebViewProviderInfo(packageName, description,
+ new WebViewProviderInfo(packageName, description, availableByDefault,
readSignatures(parser)));
}
else {
diff --git a/core/java/android/webkit/WebViewProviderInfo.java b/core/java/android/webkit/WebViewProviderInfo.java
index ac5b670..3f50fe2 100644
--- a/core/java/android/webkit/WebViewProviderInfo.java
+++ b/core/java/android/webkit/WebViewProviderInfo.java
@@ -40,9 +40,11 @@
public WebViewPackageNotFoundException(Exception e) { super(e); }
}
- public WebViewProviderInfo(String packageName, String description, String[] signatures) {
+ public WebViewProviderInfo(String packageName, String description, String availableByDefault,
+ String[] signatures) {
this.packageName = packageName;
this.description = description;
+ this.availableByDefault = availableByDefault.equals("true");
this.signatures = signatures;
}
@@ -89,6 +91,39 @@
return false;
}
+ /**
+ * Returns whether this package is enabled.
+ * This state can be changed by the user from Settings->Apps
+ */
+ public boolean isEnabled() {
+ try {
+ PackageManager pm = AppGlobals.getInitialApplication().getPackageManager();
+ int enabled_state = pm.getApplicationEnabledSetting(packageName);
+ switch (enabled_state) {
+ case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+ return true;
+ case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+ ApplicationInfo applicationInfo = getPackageInfo().applicationInfo;
+ return applicationInfo.enabled;
+ default:
+ return false;
+ }
+ } catch (WebViewPackageNotFoundException e) {
+ return false;
+ } catch (IllegalArgumentException e) {
+ // Thrown by PackageManager.getApplicationEnabledSetting if the package does not exist
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether the provider is always available as long as it is valid.
+ * If this returns false, the provider will only be used if the user chose this provider.
+ */
+ public boolean isAvailableByDefault() {
+ return availableByDefault;
+ }
+
public PackageInfo getPackageInfo() {
if (packageInfo == null) {
try {
@@ -135,6 +170,7 @@
// fields read from framework resource
public String packageName;
public String description;
+ private boolean availableByDefault;
private String[] signatures;
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 40eb375..ce4fc06 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -27,7 +27,6 @@
*/
public class MetricsLogger implements MetricsConstants {
// Temporary constants go here, to await migration to MetricsConstants.
- public static final int QS_LOCK_TILE = 257;
public static final int QS_USER_TILE = 258;
public static final int QS_BATTERY_TILE = 259;
public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
@@ -62,6 +61,7 @@
* credentials UI.
*/
public static final int PROFILE_CHALLENGE = 271;
+ public static final int QS_BATTERY_DETAIL = 272;
public static void visible(Context context, int category) throws IllegalArgumentException {
if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
index 1b44ff3..de54d96 100644
--- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -99,6 +99,9 @@
mResizingBackgroundDrawable = resizingBackgroundDrawable;
mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable;
mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable;
+ if (mCaptionBackgroundDrawable == null) {
+ mCaptionBackgroundDrawable = mResizingBackgroundDrawable;
+ }
if (statusBarColor != 0) {
mStatusBarColor = new ColorDrawable(statusBarColor);
addSystemBarNodeIfNeeded();
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index cc2f714..cea9867 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1616,14 +1616,8 @@
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
- mResizingBackgroundDrawable = getResizingBackgroundDrawable(
- mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource);
- if (mCaptionBackgroundDrawable == null) {
- mCaptionBackgroundDrawable = getContext().getDrawable(
- R.drawable.decor_caption_title_focused);
- }
-
if (mBackdropFrameRenderer != null) {
+ loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState));
@@ -1645,6 +1639,17 @@
initializeElevation();
}
+ private void loadBackgroundDrawablesIfNeeded() {
+ if (mResizingBackgroundDrawable == null) {
+ mResizingBackgroundDrawable = getResizingBackgroundDrawable(
+ mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource);
+ }
+ if (mCaptionBackgroundDrawable == null) {
+ mCaptionBackgroundDrawable = getContext().getDrawable(
+ R.drawable.decor_caption_title_focused);
+ }
+ }
+
// Free floating overlapping windows require a caption.
private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
DecorCaptionView decorCaptionView = null;
@@ -1815,6 +1820,7 @@
}
final ThreadedRenderer renderer = getHardwareRenderer();
if (renderer != null) {
+ loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState));
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
rename to core/java/com/android/internal/policy/DividerSnapAlgorithm.java
index e43d531..e79f1b8 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
@@ -14,18 +14,19 @@
* limitations under the License.
*/
-package com.android.systemui.stackdivider;
+package com.android.internal.policy;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Rect;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-
import java.util.ArrayList;
/**
* Calculates the snap targets and the snap position given a position and a velocity. All positions
* here are to be interpreted as the left/top edge of the divider rectangle.
+ *
+ * @hide
*/
public class DividerSnapAlgorithm {
@@ -44,8 +45,7 @@
*/
private static final int SNAP_ONLY_1_1 = 2;
- private final Context mContext;
- private final FlingAnimationUtils mFlingAnimationUtils;
+ private final float mMinFlingVelocityPxPerSecond;
private final int mDisplayWidth;
private final int mDisplayHeight;
private final int mDividerSize;
@@ -63,18 +63,17 @@
private final SnapTarget mDismissStartTarget;
private final SnapTarget mDismissEndTarget;
- public DividerSnapAlgorithm(Context ctx, FlingAnimationUtils flingAnimationUtils,
+ public DividerSnapAlgorithm(Resources res, float minFlingVelocityPxPerSecond,
int displayWidth, int displayHeight, int dividerSize, boolean isHorizontalDivision,
Rect insets) {
- mContext = ctx;
- mFlingAnimationUtils = flingAnimationUtils;
+ mMinFlingVelocityPxPerSecond = minFlingVelocityPxPerSecond;
mDividerSize = dividerSize;
mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight;
mInsets.set(insets);
- mSnapMode = ctx.getResources().getInteger(
+ mSnapMode = res.getInteger(
com.android.internal.R.integer.config_dockedStackDividerSnapMode);
- mFixedRatio = ctx.getResources().getFraction(
+ mFixedRatio = res.getFraction(
com.android.internal.R.fraction.docked_stack_divider_fixed_ratio, 1, 1);
calculateTargets(isHorizontalDivision);
mFirstSplitTarget = mTargets.get(1);
@@ -84,7 +83,7 @@
}
public SnapTarget calculateSnapTarget(int position, float velocity) {
- if (Math.abs(velocity) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ if (Math.abs(velocity) < mMinFlingVelocityPxPerSecond) {
return snap(position);
}
if (position < mFirstSplitTarget.position && velocity < 0) {
@@ -100,6 +99,17 @@
}
}
+ public SnapTarget calculateNonDismissingSnapTarget(int position) {
+ SnapTarget target = snap(position);
+ if (target == mDismissStartTarget) {
+ return mFirstSplitTarget;
+ } else if (target == mDismissEndTarget) {
+ return mLastSplitTarget;
+ } else {
+ return target;
+ }
+ }
+
public float calculateDismissingFraction(int position) {
if (position < mFirstSplitTarget.position) {
return 1f - (float) position / mFirstSplitTarget.position;
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java
new file mode 100644
index 0000000..25a060e
--- /dev/null
+++ b/core/java/com/android/internal/policy/DockedDividerUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy;
+
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+/**
+ * Utility functions for docked stack divider used by both window manager and System UI.
+ *
+ * @hide
+ */
+public class DockedDividerUtils {
+
+ public static void calculateBoundsForPosition(int position, int dockSide, Rect outRect,
+ int displayWidth, int displayHeight, int dividerSize) {
+ outRect.set(0, 0, displayWidth, displayHeight);
+ switch (dockSide) {
+ case WindowManager.DOCKED_LEFT:
+ outRect.right = position;
+ break;
+ case WindowManager.DOCKED_TOP:
+ outRect.bottom = position;
+ break;
+ case WindowManager.DOCKED_RIGHT:
+ outRect.left = position + dividerSize;
+ break;
+ case WindowManager.DOCKED_BOTTOM:
+ outRect.top = position + dividerSize;
+ break;
+ }
+ if (outRect.left > outRect.right) {
+ outRect.left = outRect.right;
+ }
+ if (outRect.top > outRect.bottom) {
+ outRect.top = outRect.bottom;
+ }
+ if (outRect.right < outRect.left) {
+ outRect.right = outRect.left;
+ }
+ if (outRect.bottom < outRect.top) {
+ outRect.bottom = outRect.top;
+ }
+ }
+
+ public static int calculatePositionForBounds(Rect bounds, int dockSide, int dividerSize) {
+ switch (dockSide) {
+ case WindowManager.DOCKED_LEFT:
+ return bounds.right;
+ case WindowManager.DOCKED_TOP:
+ return bounds.bottom;
+ case WindowManager.DOCKED_RIGHT:
+ return bounds.left - dividerSize;
+ case WindowManager.DOCKED_BOTTOM:
+ return bounds.top - dividerSize;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 16bf9dd..ca1334c 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -26,6 +26,7 @@
import java.lang.reflect.Array;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -372,6 +373,10 @@
return (array != null) ? array.clone() : null;
}
+ public static @Nullable <T> ArraySet<T> cloneOrNull(@Nullable ArraySet<T> array) {
+ return (array != null) ? new ArraySet<T>(array) : null;
+ }
+
public static @NonNull <T> ArraySet<T> add(@Nullable ArraySet<T> cur, T val) {
if (cur == null) {
cur = new ArraySet<>();
@@ -420,6 +425,16 @@
return (cur != null) ? cur.contains(val) : false;
}
+ public static @Nullable <T> T[] trimToSize(@Nullable T[] array, int size) {
+ if (array == null || size == 0) {
+ return null;
+ } else if (array.length == size) {
+ return array;
+ } else {
+ return Arrays.copyOf(array, size);
+ }
+ }
+
/**
* Returns true if the two ArrayLists are equal with respect to the objects they contain.
* The objects must be in the same order and be reference equal (== not .equals()).
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 21ba793..75077df 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1070,6 +1070,12 @@
<permission android:name="android.permission.READ_WIFI_CREDENTIAL"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows applications to change tether state and run
+ tether carrier provisioning.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.TETHER_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi @hide Allow system apps to receive broadcast
when a wifi network credential is changed.
<p>Not for use by third-party applications. -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 6d505a5..e0f9eca 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3316,7 +3316,14 @@
</p>
-->
<attr name="canControlMagnification" format="boolean" />
- <!-- Short description of the accessibility serivce purpose or behavior.-->
+ <!-- Attribute whether the accessibility service wants to be able to perform gestures.
+ <p>
+ Required to allow setting the {@link android.accessibilityservice
+ #AccessibilityServiceInfo#FLAG_CAN_PERFORM_GESTURES} flag.
+ </p>
+ -->
+ <attr name="canPerformGestures" format="boolean" />
+ <!-- Short description of the accessibility service purpose or behavior.-->
<attr name="description" />
</declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ebb1b37..acea461 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2688,6 +2688,7 @@
<public type="attr" name="tickMark" />
<public type="attr" name="tickMarkTint" />
<public type="attr" name="tickMarkTintMode" />
+ <public type="attr" name="canPerformGestures" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
<public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index be9ba62..997371e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -652,6 +652,12 @@
<string name="capability_desc_canControlMagnification">Control the display\'s zoom level and
positioning.</string>
+ <!-- Title for the capability of an accessibility service to perform gestures. -->
+ <string name="capability_title_canPerformGestures">Perform gestures</string>
+ <!-- Description for the capability of an accessibility service to perform gestures. -->
+ <string name="capability_desc_canPerformGestures">Can tap, swipe, pinch, and perform other
+ gestures.</string>
+
<!-- Permissions -->
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a4654e8..706dd20 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -561,6 +561,8 @@
<java-symbol type="string" name="capability_title_canRetrieveWindowContent" />
<java-symbol type="string" name="capability_desc_canControlMagnification" />
<java-symbol type="string" name="capability_title_canControlMagnification" />
+ <java-symbol type="string" name="capability_desc_canPerformGestures" />
+ <java-symbol type="string" name="capability_title_canPerformGestures" />
<java-symbol type="string" name="cfTemplateForwarded" />
<java-symbol type="string" name="cfTemplateForwardedTime" />
<java-symbol type="string" name="cfTemplateNotForwarded" />
diff --git a/core/res/res/xml/config_webview_packages.xml b/core/res/res/xml/config_webview_packages.xml
index fd443c1..f062b59 100644
--- a/core/res/res/xml/config_webview_packages.xml
+++ b/core/res/res/xml/config_webview_packages.xml
@@ -16,6 +16,6 @@
<webviewproviders>
<!-- The default WebView implementation -->
- <webviewprovider description="Android WebView" packageName="com.android.webview">
+ <webviewprovider description="Android WebView" packageName="com.android.webview" availableByDefault="true">
</webviewprovider>
</webviewproviders>
diff --git a/core/tests/ConnectivityManagerTest/AndroidManifest.xml b/core/tests/ConnectivityManagerTest/AndroidManifest.xml
index 6bd8f6e..a391e1f 100644
--- a/core/tests/ConnectivityManagerTest/AndroidManifest.xml
+++ b/core/tests/ConnectivityManagerTest/AndroidManifest.xml
@@ -75,6 +75,7 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<!-- This permission is added for API call setAirplaneMode() in ConnectivityManager -->
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
diff --git a/core/tests/coretests/src/android/util/LocaleListTest.java b/core/tests/coretests/src/android/util/LocaleListTest.java
new file mode 100644
index 0000000..de1382d
--- /dev/null
+++ b/core/tests/coretests/src/android/util/LocaleListTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.util;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.LocaleList;
+
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+public class LocaleListTest extends TestCase {
+ @SmallTest
+ public void testConstructor() throws Exception {
+ LocaleList ll;
+ ll = new LocaleList(Locale.forLanguageTag("fr"), null);
+ assertEquals("fr", ll.toLanguageTags());
+
+ ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.getEmptyLocaleList());
+ assertEquals("fr", ll.toLanguageTags());
+
+ ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("fr"));
+ assertEquals("fr", ll.toLanguageTags());
+
+ ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("de"));
+ assertEquals("fr,de", ll.toLanguageTags());
+
+ ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("de,ja"));
+ assertEquals("fr,de,ja", ll.toLanguageTags());
+
+ ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("de,fr,ja"));
+ assertEquals("fr,de,ja", ll.toLanguageTags());
+
+ ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("de,fr"));
+ assertEquals("fr,de", ll.toLanguageTags());
+
+ ll = new LocaleList(Locale.forLanguageTag("fr"), LocaleList.forLanguageTags("fr,de"));
+ assertEquals("fr,de", ll.toLanguageTags());
+ }
+
+ @SmallTest
+ public void testConstructor_nullThrows() throws Exception {
+ try {
+ final LocaleList ll = new LocaleList(null, LocaleList.getEmptyLocaleList());
+ fail("Constructing with locale and locale list should throw with a null locale.");
+ } catch (Throwable e) {
+ assertEquals(NullPointerException.class, e.getClass());
+ }
+ }
+
+ @SmallTest
+ public void testGetDefault_localeSetDefaultCalledButNoChangeNecessary() throws Exception {
+ final Locale originalLocale = Locale.getDefault();
+ final LocaleList originalLocaleList = LocaleList.getDefault();
+ final int originalLocaleIndex = originalLocaleList.indexOf(originalLocale);
+
+ // This simulates a situation potentially set by the system processes
+ LocaleList.setDefault(LocaleList.forLanguageTags("ae,en,ja"), 1 /* en */);
+
+ // check our assumptions about input
+ assertEquals("en", Locale.getDefault().toLanguageTag());
+ final LocaleList firstResult = LocaleList.getDefault();
+ assertEquals("ae,en,ja", LocaleList.getDefault().toLanguageTags());
+
+ Locale.setDefault(Locale.forLanguageTag("ae"));
+ assertSame(firstResult, LocaleList.getDefault());
+
+ // restore the original values
+ LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
+ }
+}
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 651b453..e2150c0 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -82,8 +82,13 @@
*/
public static final int PADDING_MODE_STACK = 1;
- /** Value used for undefined start and end insets. */
- private static final int UNDEFINED_INSET = Integer.MIN_VALUE;
+ /**
+ * Value used for undefined start and end insets.
+ *
+ * @see #getLayerInsetStart(int)
+ * @see #getLayerInsetEnd(int)
+ */
+ public static final int UNDEFINED_INSET = Integer.MIN_VALUE;
LayerState mLayerState;
@@ -867,7 +872,8 @@
/**
* @param index the index of the layer
- * @return number of pixels to inset from the start bound
+ * @return the number of pixels to inset from the start bound, or
+ * {@link #UNDEFINED_INSET} if not specified
* @attr ref android.R.styleable#LayerDrawableItem_start
*/
public int getLayerInsetStart(int index) {
@@ -877,7 +883,8 @@
/**
* @param index the index of the layer to adjust
- * @param e number of pixels to inset from the end bound
+ * @param e number of pixels to inset from the end bound, or
+ * {@link #UNDEFINED_INSET} if not specified
* @attr ref android.R.styleable#LayerDrawableItem_end
*/
public void setLayerInsetEnd(int index, int e) {
@@ -977,34 +984,33 @@
computeStackedPadding(padding);
}
+ final int paddingT = layerState.mPaddingTop;
+ final int paddingB = layerState.mPaddingBottom;
+
+ // Resolve padding for RTL. Relative padding overrides absolute
+ // padding.
+ final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
+ final int paddingRtlL = isLayoutRtl ? layerState.mPaddingEnd : layerState.mPaddingStart;
+ final int paddingRtlR = isLayoutRtl ? layerState.mPaddingStart : layerState.mPaddingEnd;
+ final int paddingL = paddingRtlL >= 0 ? paddingRtlL : layerState.mPaddingLeft;
+ final int paddingR = paddingRtlR >= 0 ? paddingRtlR : layerState.mPaddingRight;
+
// If padding was explicitly specified (e.g. not -1) then override the
// computed padding in that dimension.
- if (layerState.mPaddingTop >= 0) {
- padding.top = layerState.mPaddingTop;
+ if (paddingL >= 0) {
+ padding.left = paddingL;
}
- if (layerState.mPaddingBottom >= 0) {
- padding.bottom = layerState.mPaddingBottom;
+ if (paddingT >= 0) {
+ padding.top = paddingT;
}
- final int paddingRtlLeft;
- final int paddingRtlRight;
- if (getLayoutDirection() == LayoutDirection.RTL) {
- paddingRtlLeft = layerState.mPaddingEnd;
- paddingRtlRight = layerState.mPaddingStart;
- } else {
- paddingRtlLeft = layerState.mPaddingStart;
- paddingRtlRight = layerState.mPaddingEnd;
+ if (paddingR >= 0) {
+ padding.right = paddingR;
}
- final int paddingLeft = paddingRtlLeft >= 0 ? paddingRtlLeft : layerState.mPaddingLeft;
- if (paddingLeft >= 0) {
- padding.left = paddingLeft;
- }
-
- final int paddingRight = paddingRtlRight >= 0 ? paddingRtlRight : layerState.mPaddingRight;
- if (paddingRight >= 0) {
- padding.right = paddingRight;
+ if (paddingB >= 0) {
+ padding.bottom = paddingB;
}
return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0;
@@ -1471,58 +1477,59 @@
}
private void updateLayerBounds(Rect bounds) {
- int padL = 0;
- int padT = 0;
- int padR = 0;
- int padB = 0;
+ int paddingL = 0;
+ int paddingT = 0;
+ int paddingR = 0;
+ int paddingB = 0;
final Rect outRect = mTmpOutRect;
final int layoutDirection = getLayoutDirection();
- final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
+ final boolean isLayoutRtl = layoutDirection == LayoutDirection.RTL;
+ final boolean isPaddingNested = mLayerState.mPaddingMode == PADDING_MODE_NEST;
final ChildDrawable[] array = mLayerState.mChildren;
- final int N = mLayerState.mNum;
- for (int i = 0; i < N; i++) {
+
+ for (int i = 0, count = mLayerState.mNum; i < count; i++) {
final ChildDrawable r = array[i];
final Drawable d = r.mDrawable;
if (d == null) {
continue;
}
- final Rect container = mTmpContainer;
- container.set(d.getBounds());
+ final int insetT = r.mInsetT;
+ final int insetB = r.mInsetB;
- // Take the resolved layout direction into account. If start / end
- // padding are defined, they will be resolved (hence overriding) to
- // left / right or right / left depending on the resolved layout
- // direction. If start / end padding are not defined, use the
- // left / right ones.
- final int insetL, insetR;
- if (layoutDirection == LayoutDirection.RTL) {
- insetL = r.mInsetE == UNDEFINED_INSET ? r.mInsetL : r.mInsetE;
- insetR = r.mInsetS == UNDEFINED_INSET ? r.mInsetR : r.mInsetS;
- } else {
- insetL = r.mInsetS == UNDEFINED_INSET ? r.mInsetL : r.mInsetS;
- insetR = r.mInsetE == UNDEFINED_INSET ? r.mInsetR : r.mInsetE;
- }
+ // Resolve insets for RTL. Relative insets override absolute
+ // insets.
+ final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
+ final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
+ final int insetL = insetRtlL == UNDEFINED_INSET ? r.mInsetL : insetRtlL;
+ final int insetR = insetRtlR == UNDEFINED_INSET ? r.mInsetR : insetRtlR;
// Establish containing region based on aggregate padding and
// requested insets for the current layer.
- container.set(bounds.left + insetL + padL, bounds.top + r.mInsetT + padT,
- bounds.right - insetR - padR, bounds.bottom - r.mInsetB - padB);
+ final Rect container = mTmpContainer;
+ container.set(bounds.left + insetL + paddingL, bounds.top + insetT + paddingT,
+ bounds.right - insetR - paddingR, bounds.bottom - insetB - paddingB);
- // Apply resolved gravity to drawable based on resolved size.
- final int gravity = resolveGravity(r.mGravity, r.mWidth, r.mHeight,
- d.getIntrinsicWidth(), d.getIntrinsicHeight());
- final int w = r.mWidth < 0 ? d.getIntrinsicWidth() : r.mWidth;
- final int h = r.mHeight < 0 ? d.getIntrinsicHeight() : r.mHeight;
- Gravity.apply(gravity, w, h, container, outRect, layoutDirection);
+ // Compute a reasonable default gravity based on the intrinsic and
+ // explicit dimensions, if specified.
+ final int intrinsicW = d.getIntrinsicWidth();
+ final int intrinsicH = d.getIntrinsicHeight();
+ final int layerW = r.mWidth;
+ final int layerH = r.mHeight;
+ final int gravity = resolveGravity(r.mGravity, layerW, layerH, intrinsicW, intrinsicH);
+
+ // Explicit dimensions override intrinsic dimensions.
+ final int resolvedW = layerW < 0 ? intrinsicW : layerW;
+ final int resolvedH = layerH < 0 ? intrinsicH : layerH;
+ Gravity.apply(gravity, resolvedW, resolvedH, container, outRect, layoutDirection);
d.setBounds(outRect);
- if (nest) {
- padL += mPaddingL[i];
- padR += mPaddingR[i];
- padT += mPaddingT[i];
- padB += mPaddingB[i];
+ if (isPaddingNested) {
+ paddingL += mPaddingL[i];
+ paddingR += mPaddingR[i];
+ paddingT += mPaddingT[i];
+ paddingB += mPaddingB[i];
}
}
}
@@ -1578,6 +1585,7 @@
int padR = 0;
final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
+ final boolean isLayoutRtl = getLayoutDirection() == LayoutDirection.RTL;
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
for (int i = 0; i < N; i++) {
@@ -1591,15 +1599,10 @@
// left / right or right / left depending on the resolved layout
// direction. If start / end padding are not defined, use the
// left / right ones.
- final int insetL, insetR;
- final int layoutDirection = getLayoutDirection();
- if (layoutDirection == LayoutDirection.RTL) {
- insetL = r.mInsetE == UNDEFINED_INSET ? r.mInsetL : r.mInsetE;
- insetR = r.mInsetS == UNDEFINED_INSET ? r.mInsetR : r.mInsetS;
- } else {
- insetL = r.mInsetS == UNDEFINED_INSET ? r.mInsetL : r.mInsetS;
- insetR = r.mInsetE == UNDEFINED_INSET ? r.mInsetR : r.mInsetE;
- }
+ final int insetRtlL = isLayoutRtl ? r.mInsetE : r.mInsetS;
+ final int insetRtlR = isLayoutRtl ? r.mInsetS : r.mInsetE;
+ final int insetL = insetRtlL == UNDEFINED_INSET ? r.mInsetL : insetRtlL;
+ final int insetR = insetRtlR == UNDEFINED_INSET ? r.mInsetR : insetRtlR;
// Don't apply padding and insets for children that don't have
// an intrinsic dimension.
@@ -1659,8 +1662,8 @@
if (r.mDrawable != null) {
final Rect rect = mTmpRect;
r.mDrawable.getPadding(rect);
- if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] ||
- rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
+ if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i]
+ || rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
mPaddingL[i] = rect.left;
mPaddingT[i] = rect.top;
mPaddingR[i] = rect.right;
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 11056d4..0932e89 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -109,7 +109,8 @@
BakedOpDispatcher.cpp \
BakedOpRenderer.cpp \
BakedOpState.cpp \
- OpReorderer.cpp \
+ FrameReorderer.cpp \
+ LayerReorderer.cpp \
RecordingCanvas.cpp
hwui_cflags += -DHWUI_NEW_OPS
@@ -236,8 +237,8 @@
ifeq (true, $(HWUI_NEW_OPS))
LOCAL_SRC_FILES += \
tests/unit/BakedOpStateTests.cpp \
- tests/unit/RecordingCanvasTests.cpp \
- tests/unit/OpReordererTests.cpp
+ tests/unit/FrameReordererTests.cpp \
+ tests/unit/RecordingCanvasTests.cpp
endif
include $(BUILD_NATIVE_TEST)
@@ -298,7 +299,7 @@
ifeq (true, $(HWUI_NEW_OPS))
LOCAL_SRC_FILES += \
- tests/microbench/OpReordererBench.cpp
+ tests/microbench/FrameReordererBench.cpp
endif
include $(BUILD_EXECUTABLE)
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 195d235..23aca89 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -338,8 +338,8 @@
static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state,
PathTexture& texture, const RecordedOp& op) {
Rect dest(texture.width, texture.height);
- dest.translate(texture.left + op.unmappedBounds.left - texture.offset,
- texture.top + op.unmappedBounds.top - texture.offset);
+ dest.translate(texture.left - texture.offset,
+ texture.top - texture.offset);
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
.setRoundRectClipState(state.roundRectClipState)
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index b9c13e6..c1f19a3 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -201,6 +201,7 @@
}
void BakedOpRenderer::setupStencilRectList(const ClipBase* clip) {
+ LOG_ALWAYS_FATAL_IF(clip->mode != ClipMode::RectangleList, "can't rectlist clip without rectlist");
auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList;
int quadCount = rectList.getTransformedRectanglesCount();
std::vector<Vertex> rectangleVertices;
@@ -234,6 +235,7 @@
}
void BakedOpRenderer::setupStencilRegion(const ClipBase* clip) {
+ LOG_ALWAYS_FATAL_IF(clip->mode != ClipMode::Region, "can't region clip without region");
auto&& region = reinterpret_cast<const ClipRegion*>(clip)->region;
std::vector<Vertex> regionVertices;
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index f1cc846..f0406fa 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -48,10 +48,10 @@
const Rect& clipRect = clipState->rect;
if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) {
// Rejected based on either empty clip, or bounds not intersecting with clip
- if (clipState) {
- allocator.rewindIfLastAlloc(clipState);
- clipState = nullptr;
- }
+
+ // Note: we could rewind the clipState object in situations where the clipRect is empty,
+ // but *only* if the caching logic within ClipArea was aware of the rewind.
+ clipState = nullptr;
clippedBounds.setEmpty();
} else {
// Not rejected! compute true clippedBounds and clipSideFlags
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 5c7b43f..3db28c9 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -99,6 +99,7 @@
public:
static BakedOpState* tryConstruct(LinearAllocator& allocator,
Snapshot& snapshot, const RecordedOp& recordedOp) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
BakedOpState* bakedState = new (allocator) BakedOpState(
allocator, snapshot, recordedOp, false);
if (bakedState->computedState.clippedBounds.isEmpty()) {
@@ -118,6 +119,7 @@
static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
: true;
@@ -126,6 +128,7 @@
allocator, snapshot, recordedOp, expandForStroke);
if (bakedState->computedState.clippedBounds.isEmpty()) {
// bounds are empty, so op is rejected
+ // NOTE: this won't succeed if a clip was allocated
allocator.rewindIfLastAlloc(bakedState);
return nullptr;
}
@@ -134,7 +137,7 @@
static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
- if (snapshot.getRenderTargetClip().isEmpty()) return nullptr;
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
// clip isn't empty, so construct the op
return new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr);
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/FrameReorderer.cpp
similarity index 64%
rename from libs/hwui/OpReorderer.cpp
rename to libs/hwui/FrameReorderer.cpp
index cff4f3c..4bfc0b4 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/FrameReorderer.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "OpReorderer.h"
+#include "FrameReorderer.h"
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
@@ -30,343 +30,7 @@
namespace android {
namespace uirenderer {
-class BatchBase {
-public:
- BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
- : mBatchId(batchId)
- , mMerging(merging) {
- mBounds = op->computedState.clippedBounds;
- mOps.push_back(op);
- }
-
- bool intersects(const Rect& rect) const {
- if (!rect.intersects(mBounds)) return false;
-
- for (const BakedOpState* op : mOps) {
- if (rect.intersects(op->computedState.clippedBounds)) {
- return true;
- }
- }
- return false;
- }
-
- batchid_t getBatchId() const { return mBatchId; }
- bool isMerging() const { return mMerging; }
-
- const std::vector<BakedOpState*>& getOps() const { return mOps; }
-
- void dump() const {
- ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
- this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds));
- }
-protected:
- batchid_t mBatchId;
- Rect mBounds;
- std::vector<BakedOpState*> mOps;
- bool mMerging;
-};
-
-class OpBatch : public BatchBase {
-public:
- static void* operator new(size_t size, LinearAllocator& allocator) {
- return allocator.alloc(size);
- }
-
- OpBatch(batchid_t batchId, BakedOpState* op)
- : BatchBase(batchId, op, false) {
- }
-
- void batchOp(BakedOpState* op) {
- mBounds.unionWith(op->computedState.clippedBounds);
- mOps.push_back(op);
- }
-};
-
-class MergingOpBatch : public BatchBase {
-public:
- static void* operator new(size_t size, LinearAllocator& allocator) {
- return allocator.alloc(size);
- }
-
- MergingOpBatch(batchid_t batchId, BakedOpState* op)
- : BatchBase(batchId, op, true)
- , mClipSideFlags(op->computedState.clipSideFlags) {
- }
-
- /*
- * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
- * and clip side flags. Positive bounds delta means new bounds fit in old.
- */
- static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
- float boundsDelta) {
- bool currentClipExists = currentFlags & side;
- bool newClipExists = newFlags & side;
-
- // if current is clipped, we must be able to fit new bounds in current
- if (boundsDelta > 0 && currentClipExists) return false;
-
- // if new is clipped, we must be able to fit current bounds in new
- if (boundsDelta < 0 && newClipExists) return false;
-
- return true;
- }
-
- static bool paintIsDefault(const SkPaint& paint) {
- return paint.getAlpha() == 255
- && paint.getColorFilter() == nullptr
- && paint.getShader() == nullptr;
- }
-
- static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
- return a.getAlpha() == b.getAlpha()
- && a.getColorFilter() == b.getColorFilter()
- && a.getShader() == b.getShader();
- }
-
- /*
- * Checks if a (mergeable) op can be merged into this batch
- *
- * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
- * important to consider all paint attributes used in the draw calls in deciding both a) if an
- * op tries to merge at all, and b) if the op can merge with another set of ops
- *
- * False positives can lead to information from the paints of subsequent merged operations being
- * dropped, so we make simplifying qualifications on the ops that can merge, per op type.
- */
- bool canMergeWith(BakedOpState* op) const {
- bool isTextBatch = getBatchId() == OpBatchType::Text
- || getBatchId() == OpBatchType::ColorText;
-
- // Overlapping other operations is only allowed for text without shadow. For other ops,
- // multiDraw isn't guaranteed to overdraw correctly
- if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
- if (intersects(op->computedState.clippedBounds)) return false;
- }
-
- const BakedOpState* lhs = op;
- const BakedOpState* rhs = mOps[0];
-
- if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
-
- // Identical round rect clip state means both ops will clip in the same way, or not at all.
- // As the state objects are const, we can compare their pointers to determine mergeability
- if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
- if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
-
- /* Clipping compatibility check
- *
- * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
- * clip for that side.
- */
- const int currentFlags = mClipSideFlags;
- const int newFlags = op->computedState.clipSideFlags;
- if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
- const Rect& opBounds = op->computedState.clippedBounds;
- float boundsDelta = mBounds.left - opBounds.left;
- if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
- boundsDelta = mBounds.top - opBounds.top;
- if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
-
- // right and bottom delta calculation reversed to account for direction
- boundsDelta = opBounds.right - mBounds.right;
- if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
- boundsDelta = opBounds.bottom - mBounds.bottom;
- if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
- }
-
- const SkPaint* newPaint = op->op->paint;
- const SkPaint* oldPaint = mOps[0]->op->paint;
-
- if (newPaint == oldPaint) {
- // if paints are equal, then modifiers + paint attribs don't need to be compared
- return true;
- } else if (newPaint && !oldPaint) {
- return paintIsDefault(*newPaint);
- } else if (!newPaint && oldPaint) {
- return paintIsDefault(*oldPaint);
- }
- return paintsAreEquivalent(*newPaint, *oldPaint);
- }
-
- void mergeOp(BakedOpState* op) {
- mBounds.unionWith(op->computedState.clippedBounds);
- mOps.push_back(op);
-
- // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat
- // check, and doesn't extend past a side of the clip that's in use by the merged batch.
- // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect.
- mClipSideFlags |= op->computedState.clipSideFlags;
- }
-
- int getClipSideFlags() const { return mClipSideFlags; }
- const Rect& getClipRect() const { return mBounds; }
-
-private:
- int mClipSideFlags;
-};
-
-OpReorderer::LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
- const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
- : width(width)
- , height(height)
- , repaintRect(repaintRect)
- , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
- , beginLayerOp(beginLayerOp)
- , renderNode(renderNode)
- , viewportClip(Rect(width, height)) {}
-
-// iterate back toward target to see if anything drawn since should overlap the new op
-// if no target, merging ops still iterate to find similar batch to insert after
-void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
- BatchBase** targetBatch, size_t* insertBatchIndex) const {
- for (int i = mBatches.size() - 1; i >= 0; i--) {
- BatchBase* overBatch = mBatches[i];
-
- if (overBatch == *targetBatch) break;
-
- // TODO: also consider shader shared between batch types
- if (batchId == overBatch->getBatchId()) {
- *insertBatchIndex = i + 1;
- if (!*targetBatch) break; // found insert position, quit
- }
-
- if (overBatch->intersects(clippedBounds)) {
- // NOTE: it may be possible to optimize for special cases where two operations
- // of the same batch/paint could swap order, such as with a non-mergeable
- // (clipped) and a mergeable text operation
- *targetBatch = nullptr;
- break;
- }
- }
-}
-
-void OpReorderer::LayerReorderer::deferLayerClear(const Rect& rect) {
- mClearRects.push_back(rect);
-}
-
-void OpReorderer::LayerReorderer::flushLayerClears(LinearAllocator& allocator) {
- if (CC_UNLIKELY(!mClearRects.empty())) {
- const int vertCount = mClearRects.size() * 4;
- // put the verts in the frame allocator, since
- // 1) SimpleRectsOps needs verts, not rects
- // 2) even if mClearRects stored verts, std::vectors will move their contents
- Vertex* const verts = (Vertex*) allocator.alloc(vertCount * sizeof(Vertex));
-
- Vertex* currentVert = verts;
- Rect bounds = mClearRects[0];
- for (auto&& rect : mClearRects) {
- bounds.unionWith(rect);
- Vertex::set(currentVert++, rect.left, rect.top);
- Vertex::set(currentVert++, rect.right, rect.top);
- Vertex::set(currentVert++, rect.left, rect.bottom);
- Vertex::set(currentVert++, rect.right, rect.bottom);
- }
- mClearRects.clear(); // discard rects before drawing so this method isn't reentrant
-
- // One or more unclipped saveLayers have been enqueued, with deferred clears.
- // Flush all of these clears with a single draw
- SkPaint* paint = allocator.create<SkPaint>();
- paint->setXfermodeMode(SkXfermode::kClear_Mode);
- SimpleRectsOp* op = new (allocator) SimpleRectsOp(bounds,
- Matrix4::identity(), nullptr, paint,
- verts, vertCount);
- BakedOpState* bakedState = BakedOpState::directConstruct(allocator,
- &viewportClip, bounds, *op);
-
-
- deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices);
- }
-}
-
-void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
- BakedOpState* op, batchid_t batchId) {
- if (batchId != OpBatchType::CopyToLayer) {
- // if first op after one or more unclipped saveLayers, flush the layer clears
- flushLayerClears(allocator);
- }
-
- OpBatch* targetBatch = mBatchLookup[batchId];
-
- size_t insertBatchIndex = mBatches.size();
- if (targetBatch) {
- locateInsertIndex(batchId, op->computedState.clippedBounds,
- (BatchBase**)(&targetBatch), &insertBatchIndex);
- }
-
- if (targetBatch) {
- targetBatch->batchOp(op);
- } else {
- // new non-merging batch
- targetBatch = new (allocator) OpBatch(batchId, op);
- mBatchLookup[batchId] = targetBatch;
- mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
- }
-}
-
-void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
- BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
- if (batchId != OpBatchType::CopyToLayer) {
- // if first op after one or more unclipped saveLayers, flush the layer clears
- flushLayerClears(allocator);
- }
- MergingOpBatch* targetBatch = nullptr;
-
- // Try to merge with any existing batch with same mergeId
- auto getResult = mMergingBatchLookup[batchId].find(mergeId);
- if (getResult != mMergingBatchLookup[batchId].end()) {
- targetBatch = getResult->second;
- if (!targetBatch->canMergeWith(op)) {
- targetBatch = nullptr;
- }
- }
-
- size_t insertBatchIndex = mBatches.size();
- locateInsertIndex(batchId, op->computedState.clippedBounds,
- (BatchBase**)(&targetBatch), &insertBatchIndex);
-
- if (targetBatch) {
- targetBatch->mergeOp(op);
- } else {
- // new merging batch
- targetBatch = new (allocator) MergingOpBatch(batchId, op);
- mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
-
- mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
- }
-}
-
-void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg,
- BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const {
- ATRACE_NAME("flush drawing commands");
- for (const BatchBase* batch : mBatches) {
- size_t size = batch->getOps().size();
- if (size > 1 && batch->isMerging()) {
- int opId = batch->getOps()[0]->op->opId;
- const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch);
- MergedBakedOpList data = {
- batch->getOps().data(),
- size,
- mergingBatch->getClipSideFlags(),
- mergingBatch->getClipRect()
- };
- mergedReceivers[opId](arg, data);
- } else {
- for (const BakedOpState* op : batch->getOps()) {
- unmergedReceivers[op->op->opId](arg, *op);
- }
- }
- }
-}
-
-void OpReorderer::LayerReorderer::dump() const {
- ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p",
- this, width, height, offscreenBuffer, beginLayerOp, renderNode);
- for (const BatchBase* batch : mBatches) {
- batch->dump();
- }
-}
-
-OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
+FrameReorderer::FrameReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter)
: mCanvasState(*this) {
@@ -413,11 +77,11 @@
}
}
-void OpReorderer::onViewportInitialized() {}
+void FrameReorderer::onViewportInitialized() {}
-void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+void FrameReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
-void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
+void FrameReorderer::deferNodePropsAndOps(RenderNode& node) {
const RenderProperties& properties = node.properties();
const Outline& outline = properties.getOutline();
if (properties.getAlpha() <= 0
@@ -549,7 +213,7 @@
}
template <typename V>
-void OpReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
+void FrameReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
const int size = zTranslatedNodes.size();
if (size == 0
|| (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
@@ -599,7 +263,7 @@
}
}
-void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
+void FrameReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
auto& node = *casterNodeOp.renderNode;
auto& properties = node.properties();
@@ -655,7 +319,7 @@
}
}
-void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) {
+void FrameReorderer::deferProjectedChildren(const RenderNode& renderNode) {
const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -688,15 +352,15 @@
}
/**
- * Used to define a list of lambdas referencing private OpReorderer::onXX::defer() methods.
+ * Used to define a list of lambdas referencing private FrameReorderer::onXX::defer() methods.
*
* This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
- * E.g. a BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
+ * E.g. a BitmapOp op then would be dispatched to FrameReorderer::onBitmapOp(const BitmapOp&)
*/
#define OP_RECEIVER(Type) \
- [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.defer##Type(static_cast<const Type&>(op)); },
-void OpReorderer::deferNodeOps(const RenderNode& renderNode) {
- typedef void (*OpDispatcher) (OpReorderer& reorderer, const RecordedOp& op);
+ [](FrameReorderer& reorderer, const RecordedOp& op) { reorderer.defer##Type(static_cast<const Type&>(op)); },
+void FrameReorderer::deferNodeOps(const RenderNode& renderNode) {
+ typedef void (*OpDispatcher) (FrameReorderer& reorderer, const RecordedOp& op);
static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER);
// can't be null, since DL=null node rejection happens before deferNodePropsAndOps
@@ -720,7 +384,7 @@
}
}
-void OpReorderer::deferRenderNodeOpImpl(const RenderNodeOp& op) {
+void FrameReorderer::deferRenderNodeOpImpl(const RenderNodeOp& op) {
if (op.renderNode->nothingToDraw()) return;
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
@@ -735,7 +399,7 @@
mCanvasState.restoreToCount(count);
}
-void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
+void FrameReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
if (!op.skipInOrderDraw) {
deferRenderNodeOpImpl(op);
}
@@ -745,7 +409,7 @@
* Defers an unmergeable, strokeable op, accounting correctly
* for paint's style on the bounds being computed.
*/
-void OpReorderer::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+void FrameReorderer::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior) {
// Note: here we account for stroke when baking the op
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
@@ -767,7 +431,7 @@
: (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices);
}
-void OpReorderer::deferArcOp(const ArcOp& op) {
+void FrameReorderer::deferArcOp(const ArcOp& op) {
deferStrokeableOp(op, tessBatchId(op));
}
@@ -776,7 +440,7 @@
|| state.computedState.clipState->mode == ClipMode::Rectangle;
}
-void OpReorderer::deferBitmapOp(const BitmapOp& op) {
+void FrameReorderer::deferBitmapOp(const BitmapOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
@@ -796,19 +460,19 @@
}
}
-void OpReorderer::deferBitmapMeshOp(const BitmapMeshOp& op) {
+void FrameReorderer::deferBitmapMeshOp(const BitmapMeshOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
}
-void OpReorderer::deferBitmapRectOp(const BitmapRectOp& op) {
+void FrameReorderer::deferBitmapRectOp(const BitmapRectOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
}
-void OpReorderer::deferCirclePropsOp(const CirclePropsOp& op) {
+void FrameReorderer::deferCirclePropsOp(const CirclePropsOp& op) {
// allocate a temporary oval op (with mAllocator, so it persists until render), so the
// renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
float x = *(op.x);
@@ -823,22 +487,22 @@
deferOvalOp(*resolvedOp);
}
-void OpReorderer::deferFunctorOp(const FunctorOp& op) {
+void FrameReorderer::deferFunctorOp(const FunctorOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor);
}
-void OpReorderer::deferLinesOp(const LinesOp& op) {
+void FrameReorderer::deferLinesOp(const LinesOp& op) {
batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
}
-void OpReorderer::deferOvalOp(const OvalOp& op) {
+void FrameReorderer::deferOvalOp(const OvalOp& op) {
deferStrokeableOp(op, tessBatchId(op));
}
-void OpReorderer::deferPatchOp(const PatchOp& op) {
+void FrameReorderer::deferPatchOp(const PatchOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
@@ -856,24 +520,24 @@
}
}
-void OpReorderer::deferPathOp(const PathOp& op) {
+void FrameReorderer::deferPathOp(const PathOp& op) {
deferStrokeableOp(op, OpBatchType::Bitmap);
}
-void OpReorderer::deferPointsOp(const PointsOp& op) {
+void FrameReorderer::deferPointsOp(const PointsOp& op) {
batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
}
-void OpReorderer::deferRectOp(const RectOp& op) {
+void FrameReorderer::deferRectOp(const RectOp& op) {
deferStrokeableOp(op, tessBatchId(op));
}
-void OpReorderer::deferRoundRectOp(const RoundRectOp& op) {
+void FrameReorderer::deferRoundRectOp(const RoundRectOp& op) {
deferStrokeableOp(op, tessBatchId(op));
}
-void OpReorderer::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
+void FrameReorderer::deferRoundRectPropsOp(const RoundRectPropsOp& op) {
// allocate a temporary round rect op (with mAllocator, so it persists until render), so the
// renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
const RoundRectOp* resolvedOp = new (mAllocator) RoundRectOp(
@@ -884,7 +548,7 @@
deferRoundRectOp(*resolvedOp);
}
-void OpReorderer::deferSimpleRectsOp(const SimpleRectsOp& op) {
+void FrameReorderer::deferSimpleRectsOp(const SimpleRectsOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
@@ -895,7 +559,7 @@
return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText;
}
-void OpReorderer::deferTextOp(const TextOp& op) {
+void FrameReorderer::deferTextOp(const TextOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
@@ -910,19 +574,19 @@
}
}
-void OpReorderer::deferTextOnPathOp(const TextOnPathOp& op) {
+void FrameReorderer::deferTextOnPathOp(const TextOnPathOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
}
-void OpReorderer::deferTextureLayerOp(const TextureLayerOp& op) {
+void FrameReorderer::deferTextureLayerOp(const TextureLayerOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer);
}
-void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
+void FrameReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
float contentTranslateX, float contentTranslateY,
const Rect& repaintRect,
const Vector3& lightCenter,
@@ -941,7 +605,7 @@
mLayerReorderers.emplace_back(layerWidth, layerHeight, repaintRect, beginLayerOp, renderNode);
}
-void OpReorderer::restoreForLayer() {
+void FrameReorderer::restoreForLayer() {
// restore canvas, and pop finished layer off of the stack
mCanvasState.restore();
mLayerStack.pop_back();
@@ -949,7 +613,7 @@
// TODO: defer time rejection (when bounds become empty) + tests
// Option - just skip layers with no bounds at playback + defer?
-void OpReorderer::deferBeginLayerOp(const BeginLayerOp& op) {
+void FrameReorderer::deferBeginLayerOp(const BeginLayerOp& op) {
uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
@@ -994,7 +658,7 @@
&op, nullptr);
}
-void OpReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) {
+void FrameReorderer::deferEndLayerOp(const EndLayerOp& /* ignored */) {
const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
int finishedLayerIndex = mLayerStack.back();
@@ -1022,7 +686,7 @@
}
}
-void OpReorderer::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) {
+void FrameReorderer::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) {
Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform));
boundsTransform.multiply(op.localMatrix);
@@ -1057,7 +721,7 @@
currentLayer().activeUnclippedSaveLayers.push_back(bakedState);
}
-void OpReorderer::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) {
+void FrameReorderer::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) {
LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!");
BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back();
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/FrameReorderer.h
similarity index 65%
rename from libs/hwui/OpReorderer.h
rename to libs/hwui/FrameReorderer.h
index 8d9d90b..562e6a1 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/FrameReorderer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-#ifndef ANDROID_HWUI_OP_REORDERER_H
-#define ANDROID_HWUI_OP_REORDERER_H
+#pragma once
#include "BakedOpState.h"
#include "CanvasState.h"
#include "DisplayList.h"
+#include "LayerReorderer.h"
#include "RecordedOp.h"
#include <vector>
@@ -31,114 +31,34 @@
namespace uirenderer {
class BakedOpState;
-class BatchBase;
class LayerUpdateQueue;
-class MergingOpBatch;
class OffscreenBuffer;
-class OpBatch;
class Rect;
-typedef int batchid_t;
-typedef const void* mergeid_t;
-
-namespace OpBatchType {
- enum {
- Bitmap,
- MergedPatch,
- AlphaVertices,
- Vertices,
- AlphaMaskTexture,
- Text,
- ColorText,
- Shadow,
- TextureLayer,
- Functor,
- CopyToLayer,
- CopyFromLayer,
-
- Count // must be last
- };
-}
-
-class OpReorderer : public CanvasStateClient {
- typedef void (*BakedOpReceiver)(void*, const BakedOpState&);
- typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList);
-
- /**
- * Stores the deferred render operations and state used to compute ordering
- * for a single FBO/layer.
- */
- class LayerReorderer {
- public:
- // Create LayerReorderer for Fbo0
- LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect)
- : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {};
-
- // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
- // saveLayer, renderNode is present for a HW layer.
- LayerReorderer(uint32_t width, uint32_t height,
- const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
-
- // iterate back toward target to see if anything drawn since should overlap the new op
- // if no target, merging ops still iterate to find similar batch to insert after
- void locateInsertIndex(int batchId, const Rect& clippedBounds,
- BatchBase** targetBatch, size_t* insertBatchIndex) const;
-
- void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId);
-
- // insertion point of a new batch, will hopefully be immediately after similar batch
- // (generally, should be similar shader)
- void deferMergeableOp(LinearAllocator& allocator,
- BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
-
- void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const;
-
- void deferLayerClear(const Rect& dstRect);
-
- bool empty() const {
- return mBatches.empty();
- }
-
- void clear() {
- mBatches.clear();
- }
-
- void dump() const;
-
- const uint32_t width;
- const uint32_t height;
- const Rect repaintRect;
- OffscreenBuffer* offscreenBuffer;
- const BeginLayerOp* beginLayerOp;
- const RenderNode* renderNode;
- const ClipRect viewportClip;
-
- // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
- std::vector<BakedOpState*> activeUnclippedSaveLayers;
- private:
- void flushLayerClears(LinearAllocator& allocator);
-
- std::vector<BatchBase*> mBatches;
-
- /**
- * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
- * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
- * collide, which avoids the need to resolve mergeid collisions.
- */
- std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count];
-
- // Maps batch ids to the most recent *non-merging* batch of that id
- OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
-
- std::vector<Rect> mClearRects;
- };
-
+/**
+ * Traverses all of the drawing commands from the layers and RenderNodes passed into it, preparing
+ * them to be rendered.
+ *
+ * Resolves final drawing state for each operation (including clip, alpha and matrix), and then
+ * reorder and merge each op as it is resolved for drawing efficiency. Each layer of content (either
+ * from the LayerUpdateQueue, or temporary layers created by saveLayer operations in the
+ * draw stream) will create different reorder contexts, each in its own LayerReorderer.
+ *
+ * Then the prepared or 'baked' drawing commands can be issued by calling the templated
+ * replayBakedOps() function, which will dispatch them (including any created merged op collections)
+ * to a Dispatcher and Renderer. See BakedOpDispatcher for how these baked drawing operations are
+ * resolved into Glops and rendered via BakedOpRenderer.
+ *
+ * This class is also the authoritative source for traversing RenderNodes, both for standard op
+ * traversal within a DisplayList, and for out of order RenderNode traversal for Z and projection.
+ */
+class FrameReorderer : public CanvasStateClient {
public:
- OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
+ FrameReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
- virtual ~OpReorderer() {}
+ virtual ~FrameReorderer() {}
/**
* replayBakedOps() is templated based on what class will receive ops being replayed.
@@ -253,7 +173,7 @@
BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
/**
- * Declares all OpReorderer::deferXXXXOp() methods for every RecordedOp type.
+ * Declares all FrameReorderer::deferXXXXOp() methods for every RecordedOp type.
*
* These private methods are called from within deferImpl to defer each individual op
* type differently.
@@ -287,5 +207,3 @@
}; // namespace uirenderer
}; // namespace android
-
-#endif // ANDROID_HWUI_OP_REORDERER_H
diff --git a/libs/hwui/LayerReorderer.cpp b/libs/hwui/LayerReorderer.cpp
new file mode 100644
index 0000000..9a17e93
--- /dev/null
+++ b/libs/hwui/LayerReorderer.cpp
@@ -0,0 +1,367 @@
+/*
+ * 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.
+ */
+
+#include "LayerReorderer.h"
+
+#include "BakedOpState.h"
+#include "RenderNode.h"
+#include "utils/PaintUtils.h"
+#include "utils/TraceUtils.h"
+
+#include <utils/TypeHelpers.h>
+
+namespace android {
+namespace uirenderer {
+
+class BatchBase {
+public:
+ BatchBase(batchid_t batchId, BakedOpState* op, bool merging)
+ : mBatchId(batchId)
+ , mMerging(merging) {
+ mBounds = op->computedState.clippedBounds;
+ mOps.push_back(op);
+ }
+
+ bool intersects(const Rect& rect) const {
+ if (!rect.intersects(mBounds)) return false;
+
+ for (const BakedOpState* op : mOps) {
+ if (rect.intersects(op->computedState.clippedBounds)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ batchid_t getBatchId() const { return mBatchId; }
+ bool isMerging() const { return mMerging; }
+
+ const std::vector<BakedOpState*>& getOps() const { return mOps; }
+
+ void dump() const {
+ ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
+ this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds));
+ }
+protected:
+ batchid_t mBatchId;
+ Rect mBounds;
+ std::vector<BakedOpState*> mOps;
+ bool mMerging;
+};
+
+class OpBatch : public BatchBase {
+public:
+ static void* operator new(size_t size, LinearAllocator& allocator) {
+ return allocator.alloc(size);
+ }
+
+ OpBatch(batchid_t batchId, BakedOpState* op)
+ : BatchBase(batchId, op, false) {
+ }
+
+ void batchOp(BakedOpState* op) {
+ mBounds.unionWith(op->computedState.clippedBounds);
+ mOps.push_back(op);
+ }
+};
+
+class MergingOpBatch : public BatchBase {
+public:
+ static void* operator new(size_t size, LinearAllocator& allocator) {
+ return allocator.alloc(size);
+ }
+
+ MergingOpBatch(batchid_t batchId, BakedOpState* op)
+ : BatchBase(batchId, op, true)
+ , mClipSideFlags(op->computedState.clipSideFlags) {
+ }
+
+ /*
+ * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds
+ * and clip side flags. Positive bounds delta means new bounds fit in old.
+ */
+ static inline bool checkSide(const int currentFlags, const int newFlags, const int side,
+ float boundsDelta) {
+ bool currentClipExists = currentFlags & side;
+ bool newClipExists = newFlags & side;
+
+ // if current is clipped, we must be able to fit new bounds in current
+ if (boundsDelta > 0 && currentClipExists) return false;
+
+ // if new is clipped, we must be able to fit current bounds in new
+ if (boundsDelta < 0 && newClipExists) return false;
+
+ return true;
+ }
+
+ static bool paintIsDefault(const SkPaint& paint) {
+ return paint.getAlpha() == 255
+ && paint.getColorFilter() == nullptr
+ && paint.getShader() == nullptr;
+ }
+
+ static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) {
+ // Note: don't check color, since all currently mergeable ops can merge across colors
+ return a.getAlpha() == b.getAlpha()
+ && a.getColorFilter() == b.getColorFilter()
+ && a.getShader() == b.getShader();
+ }
+
+ /*
+ * Checks if a (mergeable) op can be merged into this batch
+ *
+ * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is
+ * important to consider all paint attributes used in the draw calls in deciding both a) if an
+ * op tries to merge at all, and b) if the op can merge with another set of ops
+ *
+ * False positives can lead to information from the paints of subsequent merged operations being
+ * dropped, so we make simplifying qualifications on the ops that can merge, per op type.
+ */
+ bool canMergeWith(BakedOpState* op) const {
+ bool isTextBatch = getBatchId() == OpBatchType::Text
+ || getBatchId() == OpBatchType::ColorText;
+
+ // Overlapping other operations is only allowed for text without shadow. For other ops,
+ // multiDraw isn't guaranteed to overdraw correctly
+ if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) {
+ if (intersects(op->computedState.clippedBounds)) return false;
+ }
+
+ const BakedOpState* lhs = op;
+ const BakedOpState* rhs = mOps[0];
+
+ if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false;
+
+ // Identical round rect clip state means both ops will clip in the same way, or not at all.
+ // As the state objects are const, we can compare their pointers to determine mergeability
+ if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
+ if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
+
+ /* Clipping compatibility check
+ *
+ * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its
+ * clip for that side.
+ */
+ const int currentFlags = mClipSideFlags;
+ const int newFlags = op->computedState.clipSideFlags;
+ if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) {
+ const Rect& opBounds = op->computedState.clippedBounds;
+ float boundsDelta = mBounds.left - opBounds.left;
+ if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false;
+ boundsDelta = mBounds.top - opBounds.top;
+ if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false;
+
+ // right and bottom delta calculation reversed to account for direction
+ boundsDelta = opBounds.right - mBounds.right;
+ if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false;
+ boundsDelta = opBounds.bottom - mBounds.bottom;
+ if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false;
+ }
+
+ const SkPaint* newPaint = op->op->paint;
+ const SkPaint* oldPaint = mOps[0]->op->paint;
+
+ if (newPaint == oldPaint) {
+ // if paints are equal, then modifiers + paint attribs don't need to be compared
+ return true;
+ } else if (newPaint && !oldPaint) {
+ return paintIsDefault(*newPaint);
+ } else if (!newPaint && oldPaint) {
+ return paintIsDefault(*oldPaint);
+ }
+ return paintsAreEquivalent(*newPaint, *oldPaint);
+ }
+
+ void mergeOp(BakedOpState* op) {
+ mBounds.unionWith(op->computedState.clippedBounds);
+ mOps.push_back(op);
+
+ // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat
+ // check, and doesn't extend past a side of the clip that's in use by the merged batch.
+ // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect.
+ mClipSideFlags |= op->computedState.clipSideFlags;
+ }
+
+ int getClipSideFlags() const { return mClipSideFlags; }
+ const Rect& getClipRect() const { return mBounds; }
+
+private:
+ int mClipSideFlags;
+};
+
+LayerReorderer::LayerReorderer(uint32_t width, uint32_t height,
+ const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode)
+ : width(width)
+ , height(height)
+ , repaintRect(repaintRect)
+ , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr)
+ , beginLayerOp(beginLayerOp)
+ , renderNode(renderNode)
+ , viewportClip(Rect(width, height)) {}
+
+// iterate back toward target to see if anything drawn since should overlap the new op
+// if no target, merging ops still iterate to find similar batch to insert after
+void LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
+ BatchBase** targetBatch, size_t* insertBatchIndex) const {
+ for (int i = mBatches.size() - 1; i >= 0; i--) {
+ BatchBase* overBatch = mBatches[i];
+
+ if (overBatch == *targetBatch) break;
+
+ // TODO: also consider shader shared between batch types
+ if (batchId == overBatch->getBatchId()) {
+ *insertBatchIndex = i + 1;
+ if (!*targetBatch) break; // found insert position, quit
+ }
+
+ if (overBatch->intersects(clippedBounds)) {
+ // NOTE: it may be possible to optimize for special cases where two operations
+ // of the same batch/paint could swap order, such as with a non-mergeable
+ // (clipped) and a mergeable text operation
+ *targetBatch = nullptr;
+ break;
+ }
+ }
+}
+
+void LayerReorderer::deferLayerClear(const Rect& rect) {
+ mClearRects.push_back(rect);
+}
+
+void LayerReorderer::flushLayerClears(LinearAllocator& allocator) {
+ if (CC_UNLIKELY(!mClearRects.empty())) {
+ const int vertCount = mClearRects.size() * 4;
+ // put the verts in the frame allocator, since
+ // 1) SimpleRectsOps needs verts, not rects
+ // 2) even if mClearRects stored verts, std::vectors will move their contents
+ Vertex* const verts = (Vertex*) allocator.alloc(vertCount * sizeof(Vertex));
+
+ Vertex* currentVert = verts;
+ Rect bounds = mClearRects[0];
+ for (auto&& rect : mClearRects) {
+ bounds.unionWith(rect);
+ Vertex::set(currentVert++, rect.left, rect.top);
+ Vertex::set(currentVert++, rect.right, rect.top);
+ Vertex::set(currentVert++, rect.left, rect.bottom);
+ Vertex::set(currentVert++, rect.right, rect.bottom);
+ }
+ mClearRects.clear(); // discard rects before drawing so this method isn't reentrant
+
+ // One or more unclipped saveLayers have been enqueued, with deferred clears.
+ // Flush all of these clears with a single draw
+ SkPaint* paint = allocator.create<SkPaint>();
+ paint->setXfermodeMode(SkXfermode::kClear_Mode);
+ SimpleRectsOp* op = new (allocator) SimpleRectsOp(bounds,
+ Matrix4::identity(), nullptr, paint,
+ verts, vertCount);
+ BakedOpState* bakedState = BakedOpState::directConstruct(allocator,
+ &viewportClip, bounds, *op);
+
+
+ deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices);
+ }
+}
+
+void LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId) {
+ if (batchId != OpBatchType::CopyToLayer) {
+ // if first op after one or more unclipped saveLayers, flush the layer clears
+ flushLayerClears(allocator);
+ }
+
+ OpBatch* targetBatch = mBatchLookup[batchId];
+
+ size_t insertBatchIndex = mBatches.size();
+ if (targetBatch) {
+ locateInsertIndex(batchId, op->computedState.clippedBounds,
+ (BatchBase**)(&targetBatch), &insertBatchIndex);
+ }
+
+ if (targetBatch) {
+ targetBatch->batchOp(op);
+ } else {
+ // new non-merging batch
+ targetBatch = new (allocator) OpBatch(batchId, op);
+ mBatchLookup[batchId] = targetBatch;
+ mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+ }
+}
+
+void LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
+ if (batchId != OpBatchType::CopyToLayer) {
+ // if first op after one or more unclipped saveLayers, flush the layer clears
+ flushLayerClears(allocator);
+ }
+ MergingOpBatch* targetBatch = nullptr;
+
+ // Try to merge with any existing batch with same mergeId
+ auto getResult = mMergingBatchLookup[batchId].find(mergeId);
+ if (getResult != mMergingBatchLookup[batchId].end()) {
+ targetBatch = getResult->second;
+ if (!targetBatch->canMergeWith(op)) {
+ targetBatch = nullptr;
+ }
+ }
+
+ size_t insertBatchIndex = mBatches.size();
+ locateInsertIndex(batchId, op->computedState.clippedBounds,
+ (BatchBase**)(&targetBatch), &insertBatchIndex);
+
+ if (targetBatch) {
+ targetBatch->mergeOp(op);
+ } else {
+ // new merging batch
+ targetBatch = new (allocator) MergingOpBatch(batchId, op);
+ mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
+
+ mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+ }
+}
+
+void LayerReorderer::replayBakedOpsImpl(void* arg,
+ BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const {
+ ATRACE_NAME("flush drawing commands");
+ for (const BatchBase* batch : mBatches) {
+ size_t size = batch->getOps().size();
+ if (size > 1 && batch->isMerging()) {
+ int opId = batch->getOps()[0]->op->opId;
+ const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch);
+ MergedBakedOpList data = {
+ batch->getOps().data(),
+ size,
+ mergingBatch->getClipSideFlags(),
+ mergingBatch->getClipRect()
+ };
+ mergedReceivers[opId](arg, data);
+ } else {
+ for (const BakedOpState* op : batch->getOps()) {
+ unmergedReceivers[op->op->opId](arg, *op);
+ }
+ }
+ }
+}
+
+void LayerReorderer::dump() const {
+ ALOGD("LayerReorderer %p, %ux%u buffer %p, blo %p, rn %p",
+ this, width, height, offscreenBuffer, beginLayerOp, renderNode);
+ for (const BatchBase* batch : mBatches) {
+ batch->dump();
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/LayerReorderer.h b/libs/hwui/LayerReorderer.h
new file mode 100644
index 0000000..83cda81
--- /dev/null
+++ b/libs/hwui/LayerReorderer.h
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "ClipArea.h"
+#include "Rect.h"
+
+#include <vector>
+#include <unordered_map>
+
+struct SkRect;
+
+namespace android {
+namespace uirenderer {
+
+class BakedOpState;
+struct BeginLayerOp;
+class BatchBase;
+class LinearAllocator;
+struct MergedBakedOpList;
+class MergingOpBatch;
+class OffscreenBuffer;
+class OpBatch;
+class RenderNode;
+
+typedef int batchid_t;
+typedef const void* mergeid_t;
+
+namespace OpBatchType {
+ enum {
+ Bitmap,
+ MergedPatch,
+ AlphaVertices,
+ Vertices,
+ AlphaMaskTexture,
+ Text,
+ ColorText,
+ Shadow,
+ TextureLayer,
+ Functor,
+ CopyToLayer,
+ CopyFromLayer,
+
+ Count // must be last
+ };
+}
+
+typedef void (*BakedOpReceiver)(void*, const BakedOpState&);
+typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList);
+
+/**
+ * Stores the deferred render operations and state used to compute ordering
+ * for a single FBO/layer.
+ */
+class LayerReorderer {
+public:
+ // Create LayerReorderer for Fbo0
+ LayerReorderer(uint32_t width, uint32_t height, const Rect& repaintRect)
+ : LayerReorderer(width, height, repaintRect, nullptr, nullptr) {};
+
+ // Create LayerReorderer for an offscreen layer, where beginLayerOp is present for a
+ // saveLayer, renderNode is present for a HW layer.
+ LayerReorderer(uint32_t width, uint32_t height,
+ const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
+
+ // iterate back toward target to see if anything drawn since should overlap the new op
+ // if no target, merging ops still iterate to find similar batch to insert after
+ void locateInsertIndex(int batchId, const Rect& clippedBounds,
+ BatchBase** targetBatch, size_t* insertBatchIndex) const;
+
+ void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId);
+
+ // insertion point of a new batch, will hopefully be immediately after similar batch
+ // (generally, should be similar shader)
+ void deferMergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
+
+ void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const;
+
+ void deferLayerClear(const Rect& dstRect);
+
+ bool empty() const {
+ return mBatches.empty();
+ }
+
+ void clear() {
+ mBatches.clear();
+ }
+
+ void dump() const;
+
+ const uint32_t width;
+ const uint32_t height;
+ const Rect repaintRect;
+ OffscreenBuffer* offscreenBuffer;
+ const BeginLayerOp* beginLayerOp;
+ const RenderNode* renderNode;
+ const ClipRect viewportClip;
+
+ // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
+ std::vector<BakedOpState*> activeUnclippedSaveLayers;
+private:
+ void flushLayerClears(LinearAllocator& allocator);
+
+ std::vector<BatchBase*> mBatches;
+
+ /**
+ * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
+ * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
+ * collide, which avoids the need to resolve mergeid collisions.
+ */
+ std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count];
+
+ // Maps batch ids to the most recent *non-merging* batch of that id
+ OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
+
+ std::vector<Rect> mClearRects;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index b6f50b1..612cdfd 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -87,7 +87,7 @@
*/
class RenderNode : public VirtualLightRefBase {
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
-friend class OpReorderer;
+friend class FrameReorderer;
public:
enum DirtyPropertyMask {
GENERIC = 1 << 1,
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1af8f80..fff8e09 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -31,7 +31,7 @@
#include "utils/TimeUtils.h"
#if HWUI_NEW_OPS
-#include "OpReorderer.h"
+#include "FrameReorderer.h"
#endif
#include <cutils/properties.h>
@@ -338,7 +338,7 @@
mEglManager.damageFrame(frame, dirty);
#if HWUI_NEW_OPS
- OpReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
+ FrameReorderer reorderer(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
mRenderNodes, mLightCenter);
mLayerUpdateQueue.clear();
BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(),
diff --git a/libs/hwui/tests/microbench/OpReordererBench.cpp b/libs/hwui/tests/microbench/FrameReordererBench.cpp
similarity index 86%
rename from libs/hwui/tests/microbench/OpReordererBench.cpp
rename to libs/hwui/tests/microbench/FrameReordererBench.cpp
index 6bfe5a9..b4c9a36 100644
--- a/libs/hwui/tests/microbench/OpReordererBench.cpp
+++ b/libs/hwui/tests/microbench/FrameReordererBench.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -19,8 +19,8 @@
#include "BakedOpState.h"
#include "BakedOpDispatcher.h"
#include "BakedOpRenderer.h"
+#include "FrameReorderer.h"
#include "LayerUpdateQueue.h"
-#include "OpReorderer.h"
#include "RecordedOp.h"
#include "RecordingCanvas.h"
#include "tests/common/TestContext.h"
@@ -61,20 +61,20 @@
return vec;
}
-BENCHMARK_NO_ARG(BM_OpReorderer_defer);
-void BM_OpReorderer_defer::Run(int iters) {
+BENCHMARK_NO_ARG(BM_FrameBuilder_defer);
+void BM_FrameBuilder_defer::Run(int iters) {
auto nodes = createTestNodeList();
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
nodes, sLightCenter);
MicroBench::DoNotOptimize(&reorderer);
}
StopBenchmarkTiming();
}
-BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
-void BM_OpReorderer_deferAndRender::Run(int iters) {
+BENCHMARK_NO_ARG(BM_FrameBuilder_deferAndRender);
+void BM_FrameBuilder_deferAndRender::Run(int iters) {
TestUtils::runOnRenderThread([this, iters](RenderThread& thread) {
auto nodes = createTestNodeList();
BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128 };
@@ -84,7 +84,7 @@
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
nodes, sLightCenter);
BakedOpRenderer renderer(caches, renderState, true, lightInfo);
@@ -117,7 +117,7 @@
auto nodes = getSyncedSceneNodes(sceneName);
benchmark.StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(sEmptyLayerUpdateQueue,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue,
SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
nodes, sLightCenter);
MicroBench::DoNotOptimize(&reorderer);
@@ -136,7 +136,7 @@
benchmark.StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
- OpReorderer reorderer(sEmptyLayerUpdateQueue,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue,
SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h,
nodes, sLightCenter);
@@ -148,13 +148,13 @@
});
}
-BENCHMARK_NO_ARG(BM_OpReorderer_listview_defer);
-void BM_OpReorderer_listview_defer::Run(int iters) {
+BENCHMARK_NO_ARG(BM_FrameBuilder_listview_defer);
+void BM_FrameBuilder_listview_defer::Run(int iters) {
benchDeferScene(*this, iters, "listview");
}
-BENCHMARK_NO_ARG(BM_OpReorderer_listview_deferAndRender);
-void BM_OpReorderer_listview_deferAndRender::Run(int iters) {
+BENCHMARK_NO_ARG(BM_FrameBuilder_listview_deferAndRender);
+void BM_FrameBuilder_listview_deferAndRender::Run(int iters) {
benchDeferAndRenderScene(*this, iters, "listview");
}
diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp
index 3fd822d..b350686 100644
--- a/libs/hwui/tests/unit/BakedOpStateTests.cpp
+++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp
@@ -176,29 +176,26 @@
}
TEST(BakedOpState, tryConstruct) {
- LinearAllocator allocator;
-
Matrix4 translate100x0;
translate100x0.loadTranslate(100, 0, 0);
SkPaint paint;
ClipRect clip(Rect(100, 200));
- {
- RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint);
- auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
- BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp);
- EXPECT_EQ(nullptr, bakedState); // rejected by clip, so not constructed
- EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op
- }
- {
- RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint);
- auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
- BakedOpState* bakedState = BakedOpState::tryConstruct(allocator, *snapshot, successOp);
+ LinearAllocator allocator;
+ RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint);
+ auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
+ EXPECT_NE(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, successOp))
+ << "successOp NOT rejected by clip, so should be constructed";
+ size_t successAllocSize = allocator.usedSize();
+ EXPECT_LE(64u, successAllocSize) << "relatively large alloc for non-rejected op";
- EXPECT_NE(nullptr, bakedState); // NOT rejected by clip, so will be constructed
- EXPECT_LE(64u, allocator.usedSize()); // relatively large alloc for non-rejected op
- }
+ RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint);
+ EXPECT_EQ(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, rejectOp))
+ << "rejectOp rejected by clip, so should not be constructed";
+
+ // NOTE: this relies on the clip having already been serialized by the op above
+ EXPECT_EQ(successAllocSize, allocator.usedSize()) << "no extra allocation used for rejected op";
}
TEST(BakedOpState, tryShadowOpConstruct) {
@@ -207,15 +204,16 @@
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip
BakedOpState* bakedState = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
- EXPECT_EQ(nullptr, bakedState); // rejected by clip, so not constructed
- EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op
+ EXPECT_EQ(nullptr, bakedState) << "op should be rejected by clip, so not constructed";
+ EXPECT_EQ(0u, allocator.usedSize()) << "no serialization, even for clip,"
+ "since op is quick rejected based on snapshot clip";
}
{
auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200));
BakedOpState* bakedState = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234);
- ASSERT_NE(nullptr, bakedState); // NOT rejected by clip, so will be constructed
- EXPECT_LE(64u, allocator.usedSize()); // relatively large alloc for non-rejected op
+ ASSERT_NE(nullptr, bakedState) << "NOT rejected by clip, so op should be constructed";
+ EXPECT_LE(64u, allocator.usedSize()) << "relatively large alloc for non-rejected op";
}
}
diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/FrameReordererTests.cpp
similarity index 93%
rename from libs/hwui/tests/unit/OpReordererTests.cpp
rename to libs/hwui/tests/unit/FrameReordererTests.cpp
index 701e446..9d2eb98 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/FrameReordererTests.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -18,8 +18,8 @@
#include <BakedOpState.h>
#include <DeferredLayerUpdater.h>
+#include <FrameReorderer.h>
#include <LayerUpdateQueue.h>
-#include <OpReorderer.h>
#include <RecordedOp.h>
#include <RecordingCanvas.h>
#include <tests/common/TestUtils.h>
@@ -113,7 +113,7 @@
class FailRenderer : public TestRendererBase {};
-TEST(OpReorderer, simple) {
+TEST(FrameReorderer, simple) {
class SimpleTestRenderer : public TestRendererBase {
public:
void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
@@ -138,14 +138,14 @@
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
createSyncedNodeList(node), sLightCenter);
SimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
}
-TEST(OpReorderer, simpleStroke) {
+TEST(FrameReorderer, simpleStroke) {
class SimpleStrokeTestRenderer : public TestRendererBase {
public:
void onPointsOp(const PointsOp& op, const BakedOpState& state) override {
@@ -164,14 +164,14 @@
strokedPaint.setStrokeWidth(10);
canvas.drawPoint(50, 50, strokedPaint);
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
createSyncedNodeList(node), sLightCenter);
SimpleStrokeTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(1, renderer.getIndex());
}
-TEST(OpReorderer, simpleRejection) {
+TEST(FrameReorderer, simpleRejection) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -179,14 +179,14 @@
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-TEST(OpReorderer, simpleBatching) {
+TEST(FrameReorderer, simpleBatching) {
const int LOOPS = 5;
class SimpleBatchingTestRenderer : public TestRendererBase {
public:
@@ -214,7 +214,7 @@
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -222,7 +222,7 @@
<< "Expect number of ops = 2 * loop count";
}
-TEST(OpReorderer, clippedMerging) {
+TEST(FrameReorderer, clippedMerging) {
class ClippedMergingTestRenderer : public TestRendererBase {
public:
void onMergedBitmapOps(const MergedBakedOpList& opList) override {
@@ -255,14 +255,14 @@
canvas.drawBitmap(bitmap, 40, 70, nullptr);
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
createSyncedNodeList(node), sLightCenter);
ClippedMergingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
}
-TEST(OpReorderer, textMerging) {
+TEST(FrameReorderer, textMerging) {
class TextMergingTestRenderer : public TestRendererBase {
public:
void onMergedTextOps(const MergedBakedOpList& opList) override {
@@ -283,14 +283,14 @@
TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped
TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
createSyncedNodeList(node), sLightCenter);
TextMergingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
}
-TEST(OpReorderer, textStrikethrough) {
+TEST(FrameReorderer, textStrikethrough) {
const int LOOPS = 5;
class TextStrikethroughTestRenderer : public TestRendererBase {
public:
@@ -314,7 +314,7 @@
TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
}
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
createSyncedNodeList(node), sLightCenter);
TextStrikethroughTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -322,7 +322,7 @@
<< "Expect number of ops = 2 * loop count";
}
-RENDERTHREAD_TEST(OpReorderer, textureLayer) {
+RENDERTHREAD_TEST(FrameReorderer, textureLayer) {
class TextureLayerTestRenderer : public TestRendererBase {
public:
void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override {
@@ -348,14 +348,14 @@
canvas.drawLayer(layerUpdater.get());
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
TextureLayerTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(1, renderer.getIndex());
}
-TEST(OpReorderer, renderNode) {
+TEST(FrameReorderer, renderNode) {
class RenderNodeTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -393,13 +393,13 @@
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(parent), sLightCenter);
RenderNodeTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-TEST(OpReorderer, clipped) {
+TEST(FrameReorderer, clipped) {
class ClippedTestRenderer : public TestRendererBase {
public:
void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
@@ -416,14 +416,14 @@
canvas.drawBitmap(bitmap, 0, 0, nullptr);
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue,
SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
200, 200, createSyncedNodeList(node), sLightCenter);
ClippedTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-TEST(OpReorderer, saveLayer_simple) {
+TEST(FrameReorderer, saveLayer_simple) {
class SaveLayerSimpleTestRenderer : public TestRendererBase {
public:
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
@@ -459,14 +459,14 @@
canvas.drawRect(10, 10, 190, 190, SkPaint());
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
SaveLayerSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
}
-TEST(OpReorderer, saveLayer_nested) {
+TEST(FrameReorderer, saveLayer_nested) {
/* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
* - startTemporaryLayer2, rect2 endLayer2
* - startTemporaryLayer1, rect1, drawLayer2, endLayer1
@@ -531,14 +531,14 @@
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
createSyncedNodeList(node), sLightCenter);
SaveLayerNestedTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
}
-TEST(OpReorderer, saveLayer_contentRejection) {
+TEST(FrameReorderer, saveLayer_contentRejection) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -551,7 +551,7 @@
canvas.restore();
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
@@ -559,7 +559,7 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-TEST(OpReorderer, saveLayerUnclipped_simple) {
+TEST(FrameReorderer, saveLayerUnclipped_simple) {
class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase {
public:
void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -594,14 +594,14 @@
canvas.drawRect(0, 0, 200, 200, SkPaint());
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
SaveLayerUnclippedSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
}
-TEST(OpReorderer, saveLayerUnclipped_mergedClears) {
+TEST(FrameReorderer, saveLayerUnclipped_mergedClears) {
class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase {
public:
void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
@@ -648,7 +648,7 @@
canvas.drawRect(0, 0, 100, 100, SkPaint());
canvas.restoreToCount(restoreTo);
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
SaveLayerUnclippedMergedClearsTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -660,7 +660,7 @@
* - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer
* - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe
*/
-TEST(OpReorderer, saveLayerUnclipped_complex) {
+TEST(FrameReorderer, saveLayerUnclipped_complex) {
class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase {
public:
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
@@ -710,14 +710,14 @@
canvas.restore();
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600,
createSyncedNodeList(node), sLightCenter);
SaveLayerUnclippedComplexTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(12, renderer.getIndex());
}
-RENDERTHREAD_TEST(OpReorderer, hwLayer_simple) {
+RENDERTHREAD_TEST(FrameReorderer, hwLayer_simple) {
class HwLayerSimpleTestRenderer : public TestRendererBase {
public:
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
@@ -768,7 +768,7 @@
LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75));
- OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
syncedNodeList, sLightCenter);
HwLayerSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -778,7 +778,7 @@
*layerHandle = nullptr;
}
-RENDERTHREAD_TEST(OpReorderer, hwLayer_complex) {
+RENDERTHREAD_TEST(FrameReorderer, hwLayer_complex) {
/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
* - startRepaintLayer(child), rect(grey), endLayer
* - startTemporaryLayer, drawLayer(child), endLayer
@@ -869,7 +869,7 @@
layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200));
- OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
syncedList, sLightCenter);
HwLayerComplexTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -894,7 +894,7 @@
node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
}
-TEST(OpReorderer, zReorder) {
+TEST(FrameReorderer, zReorder) {
class ZReorderTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
@@ -918,14 +918,14 @@
drawOrderedRect(&canvas, 8);
drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
createSyncedNodeList(parent), sLightCenter);
ZReorderTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
};
-TEST(OpReorderer, projectionReorder) {
+TEST(FrameReorderer, projectionReorder) {
static const int scrollX = 5;
static const int scrollY = 10;
class ProjectionReorderTestRenderer : public TestRendererBase {
@@ -1001,7 +1001,7 @@
canvas.restore();
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
createSyncedNodeList(parent), sLightCenter);
ProjectionReorderTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -1020,7 +1020,7 @@
});
}
-TEST(OpReorderer, shadow) {
+TEST(FrameReorderer, shadow) {
class ShadowTestRenderer : public TestRendererBase {
public:
void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
@@ -1044,14 +1044,14 @@
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(parent), sLightCenter);
ShadowTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2, renderer.getIndex());
}
-TEST(OpReorderer, shadowSaveLayer) {
+TEST(FrameReorderer, shadowSaveLayer) {
class ShadowSaveLayerTestRenderer : public TestRendererBase {
public:
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
@@ -1085,14 +1085,14 @@
canvas.restoreToCount(count);
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
ShadowSaveLayerTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(5, renderer.getIndex());
}
-RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) {
+RENDERTHREAD_TEST(FrameReorderer, shadowHwLayer) {
class ShadowHwLayerTestRenderer : public TestRendererBase {
public:
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
@@ -1135,7 +1135,7 @@
auto syncedList = createSyncedNodeList(parent);
LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
- OpReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
syncedList, (Vector3) { 100, 100, 100 });
ShadowHwLayerTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -1145,7 +1145,7 @@
*layerHandle = nullptr;
}
-TEST(OpReorderer, shadowLayering) {
+TEST(FrameReorderer, shadowLayering) {
class ShadowLayeringTestRenderer : public TestRendererBase {
public:
void onShadowOp(const ShadowOp& op, const BakedOpState& state) override {
@@ -1164,7 +1164,7 @@
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(parent), sLightCenter);
ShadowLayeringTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -1192,14 +1192,14 @@
canvas.drawRect(0, 0, 100, 100, paint);
});
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
createSyncedNodeList(node), sLightCenter);
PropertyTestRenderer renderer(opValidateCallback);
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
}
-TEST(OpReorderer, renderPropOverlappingRenderingAlpha) {
+TEST(FrameReorderer, renderPropOverlappingRenderingAlpha) {
testProperty([](RenderProperties& properties) {
properties.setAlpha(0.5f);
properties.setHasOverlappingRendering(false);
@@ -1208,7 +1208,7 @@
});
}
-TEST(OpReorderer, renderPropClipping) {
+TEST(FrameReorderer, renderPropClipping) {
testProperty([](RenderProperties& properties) {
properties.setClipToBounds(true);
properties.setClipBounds(Rect(10, 20, 300, 400));
@@ -1218,7 +1218,7 @@
});
}
-TEST(OpReorderer, renderPropRevealClip) {
+TEST(FrameReorderer, renderPropRevealClip) {
testProperty([](RenderProperties& properties) {
properties.mutableRevealClip().set(true, 50, 50, 25);
}, [](const RectOp& op, const BakedOpState& state) {
@@ -1229,7 +1229,7 @@
});
}
-TEST(OpReorderer, renderPropOutlineClip) {
+TEST(FrameReorderer, renderPropOutlineClip) {
testProperty([](RenderProperties& properties) {
properties.mutableOutline().setShouldClip(true);
properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
@@ -1241,7 +1241,7 @@
});
}
-TEST(OpReorderer, renderPropTransform) {
+TEST(FrameReorderer, renderPropTransform) {
testProperty([](RenderProperties& properties) {
properties.setLeftTopRightBottom(10, 10, 110, 110);
@@ -1334,7 +1334,7 @@
});
auto nodes = createSyncedNodeList(node); // sync before querying height
- OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter);
+ FrameReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter);
SaveLayerAlphaClipTestRenderer renderer(outObservedData);
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -1342,7 +1342,7 @@
ASSERT_EQ(4, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior.";
}
-TEST(OpReorderer, renderPropSaveLayerAlphaClipBig) {
+TEST(FrameReorderer, renderPropSaveLayerAlphaClipBig) {
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
properties.setTranslationX(10); // offset rendering content
@@ -1358,7 +1358,7 @@
<< "expect content to be translated as part of being clipped";
}
-TEST(OpReorderer, renderPropSaveLayerAlphaRotate) {
+TEST(FrameReorderer, renderPropSaveLayerAlphaRotate) {
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
// Translate and rotate the view so that the only visible part is the top left corner of
@@ -1377,7 +1377,7 @@
EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix);
}
-TEST(OpReorderer, renderPropSaveLayerAlphaScale) {
+TEST(FrameReorderer, renderPropSaveLayerAlphaScale) {
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
properties.setPivotX(0);
diff --git a/media/java/android/mtp/MtpEvent.java b/media/java/android/mtp/MtpEvent.java
index 6ec16db..dc89a56 100644
--- a/media/java/android/mtp/MtpEvent.java
+++ b/media/java/android/mtp/MtpEvent.java
@@ -18,15 +18,152 @@
/**
* This class encapsulates information about a MTP event.
- * Event constants are defined by the USB-IF MTP specification.
+ * This corresponds to the events described in appendix G of the MTP specification.
*/
public class MtpEvent {
private int mEventCode = MtpConstants.EVENT_UNDEFINED;
+ // Parameters for event. The interpretation of event parameters depends upon mEventCode.
+ private int mParameter1;
+ private int mParameter2;
+ private int mParameter3;
+
/**
* Returns event code of MTP event.
* See the USB-IF MTP specification for the details of event constants.
* @return event code
*/
public int getEventCode() { return mEventCode; }
+
+ /**
+ * Obtains the first event parameter.
+ */
+ public int getParameter1() { return mParameter1; }
+
+ /**
+ * Obtains the second event parameter.
+ */
+ public int getParameter2() { return mParameter2; }
+
+ /**
+ * Obtains the third event parameter.
+ */
+ public int getParameter3() { return mParameter3; }
+
+ /**
+ * Obtains objectHandle event parameter.
+ *
+ * @see MtpConstants#EVENT_OBJECT_ADDED
+ * @see MtpConstants#EVENT_OBJECT_REMOVED
+ * @see MtpConstants#EVENT_OBJECT_INFO_CHANGED
+ * @see MtpConstants#EVENT_REQUEST_OBJECT_TRANSFER
+ * @see MtpConstants#EVENT_OBJECT_PROP_CHANGED
+ * @see MtpConstants#EVENT_OBJECT_REFERENCES_CHANGED
+ */
+ public int getObjectHandle() {
+ switch (mEventCode) {
+ case MtpConstants.EVENT_OBJECT_ADDED:
+ return mParameter1;
+ case MtpConstants.EVENT_OBJECT_REMOVED:
+ return mParameter1;
+ case MtpConstants.EVENT_OBJECT_INFO_CHANGED:
+ return mParameter1;
+ case MtpConstants.EVENT_REQUEST_OBJECT_TRANSFER:
+ return mParameter1;
+ case MtpConstants.EVENT_OBJECT_PROP_CHANGED:
+ return mParameter1;
+ case MtpConstants.EVENT_OBJECT_REFERENCES_CHANGED:
+ return mParameter1;
+ default:
+ throw new IllegalParameterAccess("objectHandle", mEventCode);
+ }
+ }
+
+ /**
+ * Obtains storageID event parameter.
+ *
+ * @see MtpConstants#EVENT_STORE_ADDED
+ * @see MtpConstants#EVENT_STORE_REMOVED
+ * @see MtpConstants#EVENT_STORE_FULL
+ * @see MtpConstants#EVENT_STORAGE_INFO_CHANGED
+ */
+ public int getStorageId() {
+ switch (mEventCode) {
+ case MtpConstants.EVENT_STORE_ADDED:
+ return mParameter1;
+ case MtpConstants.EVENT_STORE_REMOVED:
+ return mParameter1;
+ case MtpConstants.EVENT_STORE_FULL:
+ return mParameter1;
+ case MtpConstants.EVENT_STORAGE_INFO_CHANGED:
+ return mParameter1;
+ default:
+ throw new IllegalParameterAccess("storageID", mEventCode);
+ }
+ }
+
+ /**
+ * Obtains devicePropCode event parameter.
+ *
+ * @see MtpConstants#EVENT_DEVICE_PROP_CHANGED
+ */
+ public int getDevicePropCode() {
+ switch (mEventCode) {
+ case MtpConstants.EVENT_DEVICE_PROP_CHANGED:
+ return mParameter1;
+ default:
+ throw new IllegalParameterAccess("devicePropCode", mEventCode);
+ }
+ }
+
+ /**
+ * Obtains transactionID event parameter.
+ *
+ * @see MtpConstants#EVENT_CAPTURE_COMPLETE
+ */
+ public int getTransactionId() {
+ switch (mEventCode) {
+ case MtpConstants.EVENT_CAPTURE_COMPLETE:
+ return mParameter1;
+ default:
+ throw new IllegalParameterAccess("transactionID", mEventCode);
+ }
+ }
+
+ /**
+ * Obtains objectPropCode event parameter.
+ *
+ * @see MtpConstants#EVENT_OBJECT_PROP_CHANGED
+ * @see MtpConstants#EVENT_OBJECT_PROP_DESC_CHANGED
+ */
+ public int getObjectPropCode() {
+ switch (mEventCode) {
+ case MtpConstants.EVENT_OBJECT_PROP_CHANGED:
+ return mParameter2;
+ case MtpConstants.EVENT_OBJECT_PROP_DESC_CHANGED:
+ return mParameter1;
+ default:
+ throw new IllegalParameterAccess("objectPropCode", mEventCode);
+ }
+ }
+
+ /**
+ * Obtains objectFormatCode event parameter.
+ *
+ * @see MtpConstants#EVENT_OBJECT_PROP_DESC_CHANGED
+ */
+ public int getObjectFormatCode() {
+ switch (mEventCode) {
+ case MtpConstants.EVENT_OBJECT_PROP_DESC_CHANGED:
+ return mParameter2;
+ default:
+ throw new IllegalParameterAccess("objectFormatCode", mEventCode);
+ }
+ }
+
+ private static class IllegalParameterAccess extends UnsupportedOperationException {
+ public IllegalParameterAccess(String propertyName, int eventCode) {
+ super("Cannot obtain " + propertyName + " for the event: " + eventCode + ".");
+ }
+ }
}
diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp
index 4aa12c2..130dfe5 100644
--- a/media/jni/android_mtp_MtpDevice.cpp
+++ b/media/jni/android_mtp_MtpDevice.cpp
@@ -98,6 +98,9 @@
// MtpEvent fields
static jfieldID field_event_eventCode;
+static jfieldID field_event_parameter1;
+static jfieldID field_event_parameter2;
+static jfieldID field_event_parameter3;
class JavaArrayWriter {
public:
@@ -573,13 +576,17 @@
env->ThrowNew(clazz_io_exception, "");
return NULL;
}
- const int eventCode = device->reapEventRequest(seq);
+ uint32_t parameters[3];
+ const int eventCode = device->reapEventRequest(seq, ¶meters);
if (eventCode <= 0) {
env->ThrowNew(clazz_operation_canceled_exception, "");
return NULL;
}
jobject result = env->NewObject(clazz_event, constructor_event);
env->SetIntField(result, field_event_eventCode, eventCode);
+ env->SetIntField(result, field_event_parameter1, static_cast<jint>(parameters[0]));
+ env->SetIntField(result, field_event_parameter2, static_cast<jint>(parameters[1]));
+ env->SetIntField(result, field_event_parameter3, static_cast<jint>(parameters[2]));
return result;
}
@@ -832,6 +839,21 @@
ALOGE("Can't find MtpObjectInfo.mEventCode");
return -1;
}
+ field_event_parameter1 = env->GetFieldID(clazz, "mParameter1", "I");
+ if (field_event_parameter1 == NULL) {
+ ALOGE("Can't find MtpObjectInfo.mParameter1");
+ return -1;
+ }
+ field_event_parameter2 = env->GetFieldID(clazz, "mParameter2", "I");
+ if (field_event_parameter2 == NULL) {
+ ALOGE("Can't find MtpObjectInfo.mParameter2");
+ return -1;
+ }
+ field_event_parameter3 = env->GetFieldID(clazz, "mParameter3", "I");
+ if (field_event_parameter3 == NULL) {
+ ALOGE("Can't find MtpObjectInfo.mParameter3");
+ return -1;
+ }
clazz_event = (jclass)env->NewGlobalRef(clazz);
clazz = env->FindClass("android/mtp/MtpDevice");
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 359a7a97a..9c01f4f 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -161,7 +161,7 @@
* </pre>
*
*/
-public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback2 {
private final static String TAG = "GLSurfaceView";
private final static boolean LOG_ATTACH_DETACH = false;
private final static boolean LOG_THREADS = false;
@@ -542,6 +542,16 @@
}
/**
+ * This method is part of the SurfaceHolder.Callback interface, and is
+ * not normally called or subclassed by clients of GLSurfaceView.
+ */
+ @Override
+ public void surfaceRedrawNeeded(SurfaceHolder holder) {
+ mGLThread.requestRenderAndWait();
+ }
+
+
+ /**
* Inform the view that the activity is paused. The owner of this view must
* call this method when the activity is paused. Calling this method will
* pause the rendering thread.
@@ -1226,6 +1236,7 @@
mHeight = 0;
mRequestRender = true;
mRenderMode = RENDERMODE_CONTINUOUSLY;
+ mWantRenderNotification = false;
mGLSurfaceViewWeakRef = glSurfaceViewWeakRef;
}
@@ -1271,6 +1282,8 @@
mEglHelper = new EglHelper(mGLSurfaceViewWeakRef);
mHaveEglContext = false;
mHaveEglSurface = false;
+ mWantRenderNotification = false;
+
try {
GL10 gl = null;
boolean createEglContext = false;
@@ -1278,7 +1291,6 @@
boolean createGlInterface = false;
boolean lostEglContext = false;
boolean sizeChanged = false;
- boolean wantRenderNotification = false;
boolean doRenderNotification = false;
boolean askedToReleaseEglContext = false;
int w = 0;
@@ -1383,7 +1395,7 @@
if (LOG_SURFACE) {
Log.i("GLThread", "sending render notification tid=" + getId());
}
- wantRenderNotification = false;
+ mWantRenderNotification = false;
doRenderNotification = false;
mRenderComplete = true;
sGLThreadManager.notifyAll();
@@ -1422,7 +1434,7 @@
sizeChanged = true;
w = mWidth;
h = mHeight;
- wantRenderNotification = true;
+ mWantRenderNotification = true;
if (LOG_SURFACE) {
Log.i("GLThread",
"noticing that we want render notification tid="
@@ -1562,7 +1574,7 @@
break;
}
- if (wantRenderNotification) {
+ if (mWantRenderNotification) {
doRenderNotification = true;
}
}
@@ -1611,6 +1623,23 @@
}
}
+ public void requestRenderAndWait() {
+ synchronized(sGLThreadManager) {
+ mWantRenderNotification = true;
+ mRequestRender = true;
+ mRenderComplete = false;
+ sGLThreadManager.notifyAll();
+ while (!mExited && !mPaused && mRenderComplete == false) {
+ try {
+ sGLThreadManager.wait();
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ }
+ }
+
public void surfaceCreated() {
synchronized(sGLThreadManager) {
if (LOG_THREADS) {
@@ -1766,6 +1795,7 @@
private int mHeight;
private int mRenderMode;
private boolean mRequestRender;
+ private boolean mWantRenderNotification;
private boolean mRenderComplete;
private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
private boolean mSizeChanged = true;
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index 7e0649b..a3cfde8 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -15,11 +15,19 @@
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<!-- showAsAction flag impacts the behavior of SearchView.
+ When set to collapseActionView, collapsing SearchView to icon is the
+ default behavior. It would fit UX, however after expanding SearchView is
+ shown on the left site of the toolbar (replacing title). Since no way to
+ prevent this behavior was found, the flag is set to always. SearchView is
+ always visible by default and it is being collapse manually by calling
+ setIconified() method
+-->
<item
android:id="@+id/menu_search"
android:title="@string/menu_search"
android:icon="@drawable/ic_menu_search"
- android:showAsAction="always|collapseActionView"
+ android:showAsAction="always"
android:actionViewClass="android.widget.SearchView"
android:imeOptions="actionSearch" />
<item
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index 153c673..c868d34 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -33,4 +33,6 @@
<color name="item_doc_background">#fffafafa</color>
<color name="item_doc_background_selected">#ffe0f2f1</color>
+ <color name="menu_search_background">#ff676f74</color>
+
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 7f710fc..180a48e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -38,12 +38,14 @@
import android.provider.DocumentsContract.Root;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
+import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.MenuItem.OnActionExpandListener;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnFocusChangeListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
@@ -218,6 +220,7 @@
case R.id.menu_advanced:
case R.id.menu_file_size:
case R.id.menu_new_window:
+ case R.id.menu_search:
break;
default:
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
@@ -318,6 +321,8 @@
* the (abstract) directoryChanged method will be called.
* @param anim
*/
+ // TODO: Refactor the usage of the method - now it is called not only when the directory
+ // changed, but also to refresh the content of the directory while searching
final void onCurrentDirectoryChanged(int anim) {
mDirectoryContainer.setDrawDisappearingFirst(anim == ANIM_DOWN);
onDirectoryChanged(anim);
@@ -328,7 +333,11 @@
}
updateActionBar();
- invalidateOptionsMenu();
+
+ // Prevents searchView from being recreated while searching
+ if (!mSearchManager.isSearching()) {
+ invalidateOptionsMenu();
+ }
}
final List<String> getExcludedAuthorities() {
@@ -720,7 +729,7 @@
* Facade over the various search parts in the menu.
*/
final class SearchManager implements
- SearchView.OnCloseListener, OnActionExpandListener, OnQueryTextListener,
+ SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener,
DocumentsToolBar.OnActionViewCollapsedListener {
private boolean mSearchExpanded;
@@ -738,9 +747,10 @@
mView = (SearchView) mMenu.getActionView();
mActionBar.setOnActionViewCollapsedListener(this);
- mMenu.setOnActionExpandListener(this);
mView.setOnQueryTextListener(this);
mView.setOnCloseListener(this);
+ mView.setOnSearchClickListener(this);
+ mView.setOnQueryTextFocusChangeListener(this);
}
/**
@@ -793,19 +803,13 @@
* search currently.
*/
boolean cancelSearch() {
- boolean collapsed = false;
- boolean closed = false;
-
- if (mActionBar.hasExpandedActionView()) {
- mActionBar.collapseActionView();
- collapsed = true;
- }
-
if (isExpanded() || isSearching()) {
- onClose();
- closed = true;
+ // If the query string is not empty search view won't get iconified
+ mView.setQuery("", false);
+ mView.setIconified(true);
+ return true;
}
- return collapsed || closed;
+ return false;
}
boolean isSearching() {
@@ -816,6 +820,11 @@
return mSearchExpanded;
}
+ /**
+ * Clears the search.
+ * @return True if the default behavior of clearing/dismissing SearchView should be
+ * overridden. False otherwise.
+ */
@Override
public boolean onClose() {
mSearchExpanded = false;
@@ -824,33 +833,33 @@
return false;
}
- mState.currentSearch = null;
- onCurrentDirectoryChanged(ANIM_NONE);
+ mView.setBackgroundColor(
+ getResources().getColor(android.R.color.transparent, null));
+
+ // Refresh the directory if a search was done
+ if(mState.currentSearch != null) {
+ mState.currentSearch = null;
+ onCurrentDirectoryChanged(ANIM_NONE);
+ }
+
return false;
}
+ /**
+ * Sets mSearchExpanded.
+ * Called when search icon is clicked to start search.
+ * Used to detect when the view expanded instead of onMenuItemActionExpand, because
+ * SearchView has showAsAction set to always and onMenuItemAction* methods are not called.
+ */
@Override
- public boolean onMenuItemActionExpand(MenuItem item) {
+ public void onClick (View v) {
mSearchExpanded = true;
- updateActionBar();
- return true;
- }
-
- @Override
- public boolean onMenuItemActionCollapse(MenuItem item) {
- mSearchExpanded = false;
- if (mIgnoreNextCollapse) {
- mIgnoreNextCollapse = false;
- return true;
- }
- mState.currentSearch = null;
- onCurrentDirectoryChanged(ANIM_NONE);
- return true;
+ mView.setBackgroundColor(
+ getResources().getColor(R.color.menu_search_background, null));
}
@Override
public boolean onQueryTextSubmit(String query) {
- mSearchExpanded = true;
mState.currentSearch = query;
mView.clearFocus();
onCurrentDirectoryChanged(ANIM_NONE);
@@ -863,6 +872,18 @@
}
@Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if(!hasFocus) {
+ if(mState.currentSearch == null) {
+ mView.setIconified(true);
+ }
+ else if(TextUtils.isEmpty(mView.getQuery())) {
+ cancelSearch();
+ }
+ }
+ }
+
+ @Override
public void onActionViewCollapsed() {
updateActionBar();
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index ca8ef2e..223af89 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -308,8 +308,10 @@
mSearchManager.showMenu(!picking);
// No display options in recent directories
- grid.setVisible(!(picking && recents));
- list.setVisible(!(picking && recents));
+ if (picking && recents) {
+ grid.setVisible(false);
+ list.setVisible(false);
+ }
fileSize.setVisible(fileSize.isVisible() && !picking);
settings.setVisible(false);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index 4c844c4..beff196 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -297,7 +297,7 @@
for (final RootInfo root : roots) {
final RootItem item = new RootItem(root);
- if (root.isLibrary() || root.isHome()) {
+ if (root.isLibrary()) {
libraries.add(item);
} else {
others.add(item);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 84ab85e..898713f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -407,7 +407,6 @@
state.derivedMode = result.mode;
}
state.derivedSortOrder = result.sortOrder;
- ((BaseActivity) context).onStateChanged();
updateDisplayState();
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index c216c77..15b8ef3 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -7,9 +7,6 @@
import android.provider.DocumentsContract;
import android.util.Log;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
index 49b48c5..7527f54 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
+import android.mtp.MtpConstants;
+import android.mtp.MtpEvent;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.SystemClock;
@@ -32,12 +34,10 @@
@RealDeviceTest
public class MtpManagerTest extends InstrumentationTestCase {
-
private static final int TIMEOUT_MS = 1000;
UsbManager mUsbManager;
MtpManager mManager;
UsbDevice mUsbDevice;
- int mRequest;
@Override
public void setUp() throws Exception {
@@ -85,6 +85,19 @@
getInstrumentation().show(Arrays.toString(records[0].operationsSupported));
}
+ public void testEventObjectAdded() throws Exception {
+ while (true) {
+ getInstrumentation().show("Please take a photo by using connected MTP device.");
+ final CancellationSignal signal = new CancellationSignal();
+ MtpEvent event = mManager.readEvent(mUsbDevice.getDeviceId(), signal);
+ if (event.getEventCode() != MtpConstants.EVENT_OBJECT_ADDED) {
+ continue;
+ }
+ assertTrue(event.getObjectHandle() != 0);
+ break;
+ }
+ }
+
private Context getContext() {
return getInstrumentation().getContext();
}
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index b662c58..76292a1 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -150,6 +150,9 @@
<!-- Description of printer info icon. [CHAR LIMIT=50] -->
<string name="printer_info_desc">More information about this printer</string>
+ <!-- Notification that print services as disabled. [CHAR LIMIT=50] -->
+ <string name="print_services_disabled_toast">Some print services are disabled.</string>
+
<!-- Add printer dialog -->
<!-- Title for the alert dialog for selecting a print service. [CHAR LIMIT=50] -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index ea11ae4..684a1de 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -173,15 +173,19 @@
if (DEBUG) {
Log.i(LOG_TAG, "[CALLED] start()");
}
- if (mState != STATE_INITIAL) {
- throw new IllegalStateException("Cannot start in state:" + stateToString(mState));
- }
- try {
- mPrintDocumentAdapter.start();
- mState = STATE_STARTED;
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error calling start()", re);
- mState = STATE_FAILED;
+ if (mState == STATE_FAILED) {
+ Log.w(LOG_TAG, "Failed before start.");
+ } else {
+ if (mState != STATE_INITIAL) {
+ throw new IllegalStateException("Cannot start in state:" + stateToString(mState));
+ }
+ try {
+ mPrintDocumentAdapter.start();
+ mState = STATE_STARTED;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error calling start()", re);
+ mState = STATE_FAILED;
+ }
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index 5525774..cd30e26 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -207,6 +207,7 @@
PrinterInfo favoritePrinter = favoritePrinters.get(i).first;
if (!alreadyAddedPrinter.contains(favoritePrinter.getId())) {
updateAndAddPrinter(printers, favoritePrinter, discoveredPrinters);
+ alreadyAddedPrinter.add(favoritePrinter.getId());
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 81727ab..13105aa 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -65,6 +65,7 @@
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
+import android.widget.Toast;
import com.android.internal.content.PackageMonitor;
import com.android.printspooler.R;
@@ -147,6 +148,14 @@
});
registerForContextMenu(mListView);
+
+ // Display a notification about disabled services if there are disabled services
+ String disabledServicesSetting = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.DISABLED_PRINT_SERVICES);
+ if (!TextUtils.isEmpty(disabledServicesSetting)) {
+ Toast.makeText(this, getString(R.string.print_services_disabled_toast),
+ Toast.LENGTH_LONG).show();
+ }
}
@Override
diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java b/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java
index a1e3ef4..f781159 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java
@@ -134,6 +134,11 @@
public void pruneApprovedServices(List<ComponentName> serviceNamesToKeep) {
synchronized (sLock) {
Set<String> approvedServices = getApprovedServices();
+
+ if (approvedServices == null) {
+ return;
+ }
+
Set<String> newApprovedServices = new ArraySet<>(approvedServices.size());
final int numServiceNamesToKeep = serviceNamesToKeep.size();
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f7e25db..ac19cf5 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -726,4 +726,41 @@
<!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
<string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
+ <!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
+ <string name="power_discharging_duration"><xliff:g id="level">%1$s</xliff:g>
+ - approx. <xliff:g id="time">%2$s</xliff:g> left</string>
+
+ <!-- [CHAR_LIMIT=40] Label for battery level chart when charging -->
+ <string name="power_charging"><xliff:g id="level">%1$s</xliff:g> -
+ <xliff:g id="state">%2$s</xliff:g></string>
+ <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
+ <string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> -
+ <xliff:g id="time">%2$s</xliff:g> until full</string>
+ <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
+ <string name="power_charging_duration_ac"><xliff:g id="level">%1$s</xliff:g> -
+ <xliff:g id="time">%2$s</xliff:g> until full on AC</string>
+ <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
+ <string name="power_charging_duration_usb"><xliff:g id="level">%1$s</xliff:g> -
+ <xliff:g id="time">%2$s</xliff:g> until full over USB</string>
+ <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
+ <string name="power_charging_duration_wireless"><xliff:g id="level">%1$s</xliff:g> -
+ <xliff:g id="time">%2$s</xliff:g> until full from wireless</string>
+
+ <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
+ <string name="battery_info_status_unknown">Unknown</string>
+ <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging from an unknown source. -->
+ <string name="battery_info_status_charging">Charging</string>
+ <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging on AC. -->
+ <string name="battery_info_status_charging_ac">Charging on AC</string>
+ <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging over USB. -->
+ <string name="battery_info_status_charging_usb">Charging over USB</string>
+ <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging over a wireless connection. -->
+ <string name="battery_info_status_charging_wireless">Charging wirelessly</string>
+ <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
+ <string name="battery_info_status_discharging">Not charging</string>
+ <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
+ <string name="battery_info_status_not_charging">Not charging</string>
+ <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
+ <string name="battery_info_status_full">Full</string>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
new file mode 100644
index 0000000..d81bdeb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
@@ -0,0 +1,107 @@
+/*
+ * 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.settingslib;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.text.format.Formatter;
+import com.android.internal.os.BatteryStatsHelper;
+
+public class BatteryInfo {
+
+ public String mChargeLabelString;
+ public int mBatteryLevel;
+ public boolean mDischarging = true;
+ public long remainingTimeUs = 0;
+
+ public interface Callback {
+ void onBatteryInfoLoaded(BatteryInfo info);
+ }
+
+ public static void getBatteryInfo(final Context context, final Callback callback) {
+ new AsyncTask<Void, Void, BatteryStats>() {
+ @Override
+ protected BatteryStats doInBackground(Void... params) {
+ BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, true);
+ statsHelper.create((Bundle) null);
+ return statsHelper.getStats();
+ }
+
+ @Override
+ protected void onPostExecute(BatteryStats batteryStats) {
+ final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
+ Intent batteryBroadcast = context.registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ BatteryInfo batteryInfo = BatteryInfo.getBatteryInfo(context,
+ batteryBroadcast, batteryStats, elapsedRealtimeUs);
+ callback.onBatteryInfoLoaded(batteryInfo);
+ }
+ }.execute();
+ }
+
+ public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast,
+ BatteryStats stats, long elapsedRealtimeUs) {
+ BatteryInfo info = new BatteryInfo();
+ info.mBatteryLevel = Utils.getBatteryLevel(batteryBroadcast);
+ String batteryPercentString = Utils.formatPercentage(info.mBatteryLevel);
+ if (batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) == 0) {
+ final long drainTime = stats.computeBatteryTimeRemaining(elapsedRealtimeUs);
+ if (drainTime > 0) {
+ info.remainingTimeUs = drainTime;
+ String timeString = Formatter.formatShortElapsedTime(context,
+ drainTime / 1000);
+ info.mChargeLabelString = context.getResources().getString(
+ R.string.power_discharging_duration, batteryPercentString, timeString);
+ } else {
+ info.mChargeLabelString = batteryPercentString;
+ }
+ } else {
+ final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs);
+ final String statusLabel = Utils.getBatteryStatus(
+ context.getResources(), batteryBroadcast);
+ final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
+ info.mDischarging = false;
+ info.remainingTimeUs = chargeTime;
+ String timeString = Formatter.formatShortElapsedTime(context,
+ chargeTime / 1000);
+ int plugType = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ int resId;
+ if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
+ resId = R.string.power_charging_duration_ac;
+ } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
+ resId = R.string.power_charging_duration_usb;
+ } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+ resId = R.string.power_charging_duration_wireless;
+ } else {
+ resId = R.string.power_charging_duration;
+ }
+ info.mChargeLabelString = context.getResources().getString(
+ resId, batteryPercentString, timeString);
+ } else {
+ info.mChargeLabelString = context.getResources().getString(
+ R.string.power_charging, batteryPercentString, statusLabel);
+ }
+ }
+ return info;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 621a09cd..72df96d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -1,17 +1,21 @@
package com.android.settingslib;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
+import android.os.BatteryManager;
import android.os.UserManager;
-
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.CircleFramedDrawable;
-public final class Utils {
+import java.text.NumberFormat;
+
+public class Utils {
/**
* Return string resource that best describes combination of tethering
@@ -81,4 +85,57 @@
return CircleFramedDrawable.getInstance(context, UserIcons.convertToBitmap(
UserIcons.getDefaultUserIcon(user.id, /* light= */ false)));
}
+
+ /** Formats the ratio of amount/total as a percentage. */
+ public static String formatPercentage(long amount, long total) {
+ return formatPercentage(((double) amount) / total);
+ }
+
+ /** Formats an integer from 0..100 as a percentage. */
+ public static String formatPercentage(int percentage) {
+ return formatPercentage(((double) percentage) / 100.0);
+ }
+
+ /** Formats a double from 0.0..1.0 as a percentage. */
+ private static String formatPercentage(double percentage) {
+ return NumberFormat.getPercentInstance().format(percentage);
+ }
+
+ public static int getBatteryLevel(Intent batteryChangedIntent) {
+ int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 100);
+ return (level * 100) / scale;
+ }
+
+ public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
+ final Intent intent = batteryChangedIntent;
+
+ int plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ String statusString;
+ if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+ int resId;
+ if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
+ resId = R.string.battery_info_status_charging_ac;
+ } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
+ resId = R.string.battery_info_status_charging_usb;
+ } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+ resId = R.string.battery_info_status_charging_wireless;
+ } else {
+ resId = R.string.battery_info_status_charging;
+ }
+ statusString = res.getString(resId);
+ } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+ statusString = res.getString(R.string.battery_info_status_discharging);
+ } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
+ statusString = res.getString(R.string.battery_info_status_not_charging);
+ } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
+ statusString = res.getString(R.string.battery_info_status_full);
+ } else {
+ statusString = res.getString(R.string.battery_info_status_unknown);
+ }
+
+ return statusString;
+ }
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 9546c8d..6201fd6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -58,6 +58,7 @@
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
@@ -142,6 +143,9 @@
<!-- Block notifications inline notifications -->
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <!-- Access battery information -->
+ <uses-permission android:name="android.permission.BATTERY_STATS" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/res/layout/battery_detail.xml b/packages/SystemUI/res/layout/battery_detail.xml
new file mode 100644
index 0000000..ea4db4b
--- /dev/null
+++ b/packages/SystemUI/res/layout/battery_detail.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="true">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:scaleType="fitCenter"
+ android:adjustViewBounds="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="32dp" />
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toStartOf="@android:id/toggle"
+ android:layout_toEndOf="@android:id/icon"
+ android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary"
+ android:text="@string/battery_detail_switch_title" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_toStartOf="@android:id/toggle"
+ android:layout_toEndOf="@android:id/icon"
+ android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary"
+ android:text="@string/battery_detail_switch_summary" />
+
+ <Switch
+ android:id="@android:id/toggle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginEnd="16dp"
+ android:clickable="false"
+ android:textAppearance="@style/TextAppearance.QS.DetailHeader" />
+
+</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 7ac9c41..39da8d0 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -84,16 +84,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- <com.android.systemui.statusbar.phone.PanelHolder
- android:id="@+id/panel_holder"
+ <include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/transparent" >
- <include layout="@layout/status_bar_expanded"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone" />
- </com.android.systemui.statusbar.phone.PanelHolder>
+ android:visibility="gone" />
<com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/arrays.xml b/packages/SystemUI/res/values/arrays.xml
index 6102aa6..bf0cba2 100644
--- a/packages/SystemUI/res/values/arrays.xml
+++ b/packages/SystemUI/res/values/arrays.xml
@@ -37,4 +37,18 @@
<item>157</item><item>334</item>
<item>0</item> <item>334</item>
</array>
+ <array name="batterymeter_plus_points">
+ <item>3</item><item>0</item>
+ <item>5</item><item>0</item>
+ <item>5</item><item>3</item>
+ <item>8</item><item>3</item>
+ <item>8</item><item>5</item>
+ <item>5</item><item>5</item>
+ <item>5</item><item>8</item>
+ <item>3</item><item>8</item>
+ <item>3</item><item>5</item>
+ <item>0</item><item>5</item>
+ <item>0</item><item>3</item>
+ <item>3</item><item>3</item>
+ </array>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0ccc236..905da13 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -62,8 +62,6 @@
<color name="recents_task_bar_light_icon_color">#ffeeeeee</color>
<!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. -->
<color name="recents_task_bar_dark_icon_color">#99000000</color>
- <!-- The recents task bar highlight color. -->
- <color name="recents_task_bar_highlight_color">#28ffffff</color>
<!-- The lock to task button background color. -->
<color name="recents_task_view_lock_to_app_button_background_color">#ffe6e6e6</color>
<!-- The lock to task button foreground color. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 035f564..097c352 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -198,16 +198,16 @@
<dimen name="recents_task_view_rounded_corners_radius">2dp</dimen>
<!-- The min translation in the Z index for the last task. -->
- <dimen name="recents_task_view_z_min">20dp</dimen>
+ <dimen name="recents_task_view_z_min">16dp</dimen>
<!-- The max translation in the Z index for the last task. -->
- <dimen name="recents_task_view_z_max">80dp</dimen>
+ <dimen name="recents_task_view_z_max">48dp</dimen>
<!-- The amount to translate when animating the removal of a task. -->
<dimen name="recents_task_view_remove_anim_translation_x">100dp</dimen>
<!-- The amount of highlight to make on each task view. -->
- <dimen name="recents_task_view_highlight">1.5dp</dimen>
+ <dimen name="recents_task_view_highlight">1dp</dimen>
<!-- The amount to offset when animating into an affiliate group. -->
<dimen name="recents_task_view_affiliate_group_enter_offset">64dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 876c21e..4136c11 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1269,4 +1269,16 @@
<string name="color_modification_g" translatable="false">G</string>
<string name="color_modification_b" translatable="false">B</string>
+ <!-- Title of the battery settings detail panel [CHAR LIMIT=20] -->
+ <string name="battery_panel_title">Battery (<xliff:g name="pattery_percent" example="52">%1$d</xliff:g>%%)</string>
+
+ <!-- Summary of battery saver not available [CHAR LIMIT=NONE] -->
+ <string name="battery_detail_charging_summary">Battery Saver not available during charging</string>
+
+ <!-- Title of switch for battery saver [CHAR LIMIT=NONE] -->
+ <string name="battery_detail_switch_title">Battery Saver</string>
+
+ <!-- Summary of switch for battery saver [CHAR LIMIT=NONE] -->
+ <string name="battery_detail_switch_summary">Reduces performance and background data</string>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
index 3eb1271..38ae345 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterDrawable.java
@@ -49,7 +49,8 @@
private float mButtonHeightFraction;
private float mSubpixelSmoothingLeft;
private float mSubpixelSmoothingRight;
- private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
+ private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint,
+ mPlusPaint;
private float mTextHeight, mWarningTextHeight;
private int mIconTint = Color.WHITE;
@@ -60,10 +61,13 @@
private int mChargeColor;
private final float[] mBoltPoints;
private final Path mBoltPath = new Path();
+ private final float[] mPlusPoints;
+ private final Path mPlusPath = new Path();
private final RectF mFrame = new RectF();
private final RectF mButtonFrame = new RectF();
private final RectF mBoltFrame = new RectF();
+ private final RectF mPlusFrame = new RectF();
private final Path mShapePath = new Path();
private final Path mClipPath = new Path();
@@ -141,6 +145,9 @@
mBoltPaint.setColor(context.getColor(R.color.batterymeter_bolt_color));
mBoltPoints = loadBoltPoints(res);
+ mPlusPaint = new Paint(mBoltPaint);
+ mPlusPoints = loadPlusPoints(res);
+
mDarkModeBackgroundColor =
context.getColor(R.color.dark_mode_icon_color_dual_tone_background);
mDarkModeFillColor = context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
@@ -187,8 +194,8 @@
}
@Override
- public void onPowerSaveChanged() {
- mPowerSaveEnabled = mBatteryController.isPowerSave();
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ mPowerSaveEnabled = isPowerSave;
invalidateSelf();
}
@@ -207,6 +214,21 @@
return ptsF;
}
+ private static float[] loadPlusPoints(Resources res) {
+ final int[] pts = res.getIntArray(R.array.batterymeter_plus_points);
+ int maxX = 0, maxY = 0;
+ for (int i = 0; i < pts.length; i += 2) {
+ maxX = Math.max(maxX, pts[i]);
+ maxY = Math.max(maxY, pts[i + 1]);
+ }
+ final float[] ptsF = new float[pts.length];
+ for (int i = 0; i < pts.length; i += 2) {
+ ptsF[i] = (float)pts[i] / maxX;
+ ptsF[i + 1] = (float)pts[i + 1] / maxY;
+ }
+ return ptsF;
+ }
+
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
@@ -328,9 +350,9 @@
if (mPluggedIn) {
// define the bolt shape
- final float bl = mFrame.left + mFrame.width() / 4.5f;
+ final float bl = mFrame.left + mFrame.width() / 4f;
final float bt = mFrame.top + mFrame.height() / 6f;
- final float br = mFrame.right - mFrame.width() / 7f;
+ final float br = mFrame.right - mFrame.width() / 4f;
final float bb = mFrame.bottom - mFrame.height() / 10f;
if (mBoltFrame.left != bl || mBoltFrame.top != bt
|| mBoltFrame.right != br || mBoltFrame.bottom != bb) {
@@ -358,6 +380,39 @@
// otherwise cut the bolt out of the overall shape
mShapePath.op(mBoltPath, Path.Op.DIFFERENCE);
}
+ } else if (mPowerSaveEnabled) {
+ // define the plus shape
+ final float pw = mFrame.width() * 2 / 3;
+ final float pl = mFrame.left + (mFrame.width() - pw) / 2;
+ final float pt = mFrame.top + (mFrame.height() - pw) / 2;
+ final float pr = mFrame.right - (mFrame.width() - pw) / 2;
+ final float pb = mFrame.bottom - (mFrame.height() - pw) / 2;
+ if (mPlusFrame.left != pl || mPlusFrame.top != pt
+ || mPlusFrame.right != pr || mPlusFrame.bottom != pb) {
+ mPlusFrame.set(pl, pt, pr, pb);
+ mPlusPath.reset();
+ mPlusPath.moveTo(
+ mPlusFrame.left + mPlusPoints[0] * mPlusFrame.width(),
+ mPlusFrame.top + mPlusPoints[1] * mPlusFrame.height());
+ for (int i = 2; i < mPlusPoints.length; i += 2) {
+ mPlusPath.lineTo(
+ mPlusFrame.left + mPlusPoints[i] * mPlusFrame.width(),
+ mPlusFrame.top + mPlusPoints[i + 1] * mPlusFrame.height());
+ }
+ mPlusPath.lineTo(
+ mPlusFrame.left + mPlusPoints[0] * mPlusFrame.width(),
+ mPlusFrame.top + mPlusPoints[1] * mPlusFrame.height());
+ }
+
+ float boltPct = (mPlusFrame.bottom - levelTop) / (mPlusFrame.bottom - mPlusFrame.top);
+ boltPct = Math.min(Math.max(boltPct, 0), 1);
+ if (boltPct <= BOLT_LEVEL_THRESHOLD) {
+ // draw the bolt if opaque
+ c.drawPath(mPlusPath, mPlusPaint);
+ } else {
+ // otherwise cut the bolt out of the overall shape
+ mShapePath.op(mPlusPath, Path.Op.DIFFERENCE);
+ }
}
// compute percentage text
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 6cb8da4..cdbdc22 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -74,7 +74,7 @@
}
@Override
- public void onPowerSaveChanged() {
+ public void onPowerSaveChanged(boolean isPowerSave) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 7f6cda0..99028a6c 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -32,7 +32,7 @@
/**
* Docks the top-most task and opens recents.
*/
- void dockTopTask(boolean draggingInRecents, int stackCreateMode, Rect initialBounds);
+ boolean dockTopTask(boolean draggingInRecents, int stackCreateMode, Rect initialBounds);
/**
* Called during a drag-from-navbar-in gesture.
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 19b65f7..ea1c9bf 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -52,7 +52,6 @@
private static final int SHOWING_NOTHING = 0;
private static final int SHOWING_WARNING = 1;
- private static final int SHOWING_SAVER = 2;
private static final int SHOWING_INVALID_CHARGER = 3;
private static final String[] SHOWING_STRINGS = {
"SHOWING_NOTHING",
@@ -63,7 +62,6 @@
private static final String ACTION_SHOW_BATTERY_SETTINGS = "PNW.batterySettings";
private static final String ACTION_START_SAVER = "PNW.startSaver";
- private static final String ACTION_STOP_SAVER = "PNW.stopSaver";
private static final String ACTION_DISMISSED_WARNING = "PNW.dismissedWarning";
private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
@@ -77,7 +75,6 @@
private final Handler mHandler = new Handler();
private final Receiver mReceiver = new Receiver();
private final Intent mOpenBatterySettings = settings(Intent.ACTION_POWER_USAGE_SUMMARY);
- private final Intent mOpenSaverSettings = settings(Settings.ACTION_BATTERY_SAVER_SETTINGS);
private int mBatteryLevel;
private int mBucket;
@@ -86,7 +83,6 @@
private long mBucketDroppedNegativeTimeMs;
- private boolean mSaver;
private boolean mWarning;
private boolean mPlaySound;
private boolean mInvalidCharger;
@@ -101,7 +97,6 @@
@Override
public void dump(PrintWriter pw) {
- pw.print("mSaver="); pw.println(mSaver);
pw.print("mWarning="); pw.println(mWarning);
pw.print("mPlaySound="); pw.println(mPlaySound);
pw.print("mInvalidCharger="); pw.println(mInvalidCharger);
@@ -121,27 +116,15 @@
mScreenOffTime = screenOffTime;
}
- @Override
- public void showSaverMode(boolean mode) {
- mSaver = mode;
- if (mSaver && mSaverConfirmation != null) {
- mSaverConfirmation.dismiss();
- }
- updateNotification();
- }
-
private void updateNotification() {
if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
- + mPlaySound + " mSaver=" + mSaver + " mInvalidCharger=" + mInvalidCharger);
+ + mPlaySound + " mInvalidCharger=" + mInvalidCharger);
if (mInvalidCharger) {
showInvalidChargerNotification();
mShowing = SHOWING_INVALID_CHARGER;
} else if (mWarning) {
showWarningNotification();
mShowing = SHOWING_WARNING;
- } else if (mSaver) {
- showSaverNotification();
- mShowing = SHOWING_SAVER;
} else {
mNoMan.cancelAsUser(TAG_NOTIFICATION, R.id.notification_power, UserHandle.ALL);
mShowing = SHOWING_NOTHING;
@@ -165,8 +148,7 @@
}
private void showWarningNotification() {
- final int textRes = mSaver ? R.string.battery_low_percent_format_saver_started
- : R.string.battery_low_percent_format;
+ final int textRes = R.string.battery_low_percent_format;
final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
final Notification.Builder nb = new Notification.Builder(mContext)
.setSmallIcon(R.drawable.ic_power_low)
@@ -184,13 +166,9 @@
if (hasBatterySettings()) {
nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
}
- if (!mSaver) {
- nb.addAction(0,
- mContext.getString(R.string.battery_saver_start_action),
- pendingBroadcast(ACTION_START_SAVER));
- } else {
- addStopSaverAction(nb);
- }
+ nb.addAction(0,
+ mContext.getString(R.string.battery_saver_start_action),
+ pendingBroadcast(ACTION_START_SAVER));
if (mPlaySound) {
attachLowBatterySound(nb);
mPlaySound = false;
@@ -199,35 +177,6 @@
mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
}
- private void showSaverNotification() {
- final Notification.Builder nb = new Notification.Builder(mContext)
- .setSmallIcon(R.drawable.ic_power_saver)
- .setContentTitle(mContext.getString(R.string.battery_saver_notification_title))
- .setContentText(mContext.getString(R.string.battery_saver_notification_text))
- .setOngoing(true)
- .setShowWhen(false)
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(mContext.getColor(
- com.android.internal.R.color.battery_saver_mode_color));
- addStopSaverAction(nb);
- if (hasSaverSettings()) {
- nb.setContentIntent(pendingActivity(mOpenSaverSettings));
- }
- mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, nb.build(), UserHandle.ALL);
- }
-
- private void addStopSaverAction(Notification.Builder nb) {
- nb.addAction(0,
- mContext.getString(R.string.battery_saver_notification_action_text),
- pendingBroadcast(ACTION_STOP_SAVER));
- }
-
- private void dismissSaverNotification() {
- if (mSaver) Slog.i(TAG, "dismissing saver notification");
- mSaver = false;
- updateNotification();
- }
-
private PendingIntent pendingActivity(Intent intent) {
return PendingIntent.getActivityAsUser(mContext,
0, intent, 0, null, UserHandle.CURRENT);
@@ -272,10 +221,6 @@
return mOpenBatterySettings.resolveActivity(mContext.getPackageManager()) != null;
}
- private boolean hasSaverSettings() {
- return mOpenSaverSettings.resolveActivity(mContext.getPackageManager()) != null;
- }
-
@Override
public void showLowBatteryWarning(boolean playSound) {
Slog.i(TAG,
@@ -367,7 +312,6 @@
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SHOW_BATTERY_SETTINGS);
filter.addAction(ACTION_START_SAVER);
- filter.addAction(ACTION_STOP_SAVER);
filter.addAction(ACTION_DISMISSED_WARNING);
mContext.registerReceiverAsUser(this, UserHandle.ALL, filter,
android.Manifest.permission.STATUS_BAR_SERVICE, mHandler);
@@ -383,10 +327,6 @@
} else if (action.equals(ACTION_START_SAVER)) {
dismissLowBatteryNotification();
showStartSaverConfirmation();
- } else if (action.equals(ACTION_STOP_SAVER)) {
- dismissSaverNotification();
- dismissLowBatteryNotification();
- setSaverMode(false);
} else if (action.equals(ACTION_DISMISSED_WARNING)) {
dismissLowBatteryWarning();
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 9459740..522d533 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -76,10 +76,6 @@
mReceiver.init();
}
- private void setSaverMode(boolean mode) {
- mWarnings.showSaverMode(mode);
- }
-
void updateBatteryWarningLevels() {
int critLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_criticalBatteryWarningLevel);
@@ -141,11 +137,6 @@
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
mContext.registerReceiver(this, filter, null, mHandler);
- updateSaverMode();
- }
-
- private void updateSaverMode() {
- setSaverMode(mPowerManager.isPowerSaveMode());
}
@Override
@@ -210,10 +201,6 @@
mScreenOffTime = -1;
} else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
mWarnings.userSwitched();
- } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) {
- updateSaverMode();
- } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGING.equals(action)) {
- setSaverMode(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
} else {
Slog.w(TAG, "unknown intent: " + intent);
}
@@ -251,7 +238,6 @@
public interface WarningsUI {
void update(int batteryLevel, int bucket, long screenOffTime);
- void showSaverMode(boolean mode);
void dismissLowBatteryWarning();
void showLowBatteryWarning(boolean playSound);
void dismissInvalidChargerWarning();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 16fd9eb..91f88b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -466,7 +466,7 @@
MetricsLogger.visible(mContext, detailAdapter.getMetricsCategory());
announceForAccessibility(mContext.getString(
R.string.accessibility_quick_settings_detail,
- mContext.getString(detailAdapter.getTitle())));
+ detailAdapter.getTitle()));
setDetailRecord(r);
listener = mHideGridContentWhenDone;
if (r instanceof TileRecord && visibleDiff) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index b6776bb..8ce6da2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -109,7 +109,7 @@
}
public interface DetailAdapter {
- int getTitle();
+ CharSequence getTitle();
Boolean getToggleState();
View createDetailView(Context context, View convertView, ViewGroup parent);
Intent getSettingsIntent();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index 84eac65..60238fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -19,7 +19,14 @@
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Checkable;
+import android.widget.ImageView;
+import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
+import com.android.settingslib.BatteryInfo;
import com.android.systemui.BatteryMeterDrawable;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
@@ -31,8 +38,11 @@
private final BatteryMeterDrawable mDrawable;
private final BatteryController mBatteryController;
+ private final BatteryDetail mBatteryDetail = new BatteryDetail();
private int mLevel;
+ private boolean mPowerSave;
+ private boolean mCharging;
public BatteryTile(Host host) {
super(host);
@@ -48,6 +58,11 @@
}
@Override
+ public DetailAdapter getDetailAdapter() {
+ return mBatteryDetail;
+ }
+
+ @Override
public int getMetricsCategory() {
return MetricsLogger.QS_BATTERY_TILE;
}
@@ -64,8 +79,16 @@
}
@Override
+ public void setDetailListening(boolean listening) {
+ super.setDetailListening(listening);
+ if (!listening) {
+ mBatteryDetail.mCurrentView = null;
+ }
+ }
+
+ @Override
protected void handleClick() {
- mHost.startActivityDismissingKeyguard(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY));
+ showDetail(true);
}
@Override
@@ -85,11 +108,98 @@
@Override
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
mLevel = level;
+ mCharging = charging;
refreshState((Integer) level);
+ if (mBatteryDetail.mCurrentView != null) {
+ mBatteryDetail.bindView();
+ }
}
@Override
- public void onPowerSaveChanged() {
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ mPowerSave = isPowerSave;
+ if (mBatteryDetail.mCurrentView != null) {
+ mBatteryDetail.bindView();
+ }
+ }
+ private final class BatteryDetail implements DetailAdapter, View.OnClickListener {
+ private final BatteryMeterDrawable mDrawable = new BatteryMeterDrawable(mHost.getContext(),
+ new Handler(), mHost.getContext().getColor(R.color.batterymeter_frame_color));
+ private View mCurrentView;
+
+ @Override
+ public CharSequence getTitle() {
+ return mContext.getString(R.string.battery_panel_title, mLevel);
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(mContext).inflate(R.layout.battery_detail, parent,
+ false);
+ }
+ mCurrentView = convertView;
+ bindView();
+ return convertView;
+ }
+
+ private void bindView() {
+ mDrawable.onBatteryLevelChanged(100, false, false);
+ mDrawable.onPowerSaveChanged(true);
+ ((ImageView) mCurrentView.findViewById(android.R.id.icon)).setImageDrawable(mDrawable);
+ Checkable checkbox = (Checkable) mCurrentView.findViewById(android.R.id.toggle);
+ checkbox.setChecked(mPowerSave);
+ if (mCharging) {
+ BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() {
+ @Override
+ public void onBatteryInfoLoaded(BatteryInfo info) {
+ if (mCurrentView != null && mCharging) {
+ ((TextView) mCurrentView.findViewById(android.R.id.title)).setText(
+ info.mChargeLabelString);
+ }
+ }
+ });
+ ((TextView) mCurrentView.findViewById(android.R.id.summary)).setText(
+ R.string.battery_detail_charging_summary);
+ mCurrentView.setClickable(false);
+ mCurrentView.findViewById(android.R.id.icon).setVisibility(View.INVISIBLE);
+ mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.INVISIBLE);
+ } else {
+ ((TextView) mCurrentView.findViewById(android.R.id.title)).setText(
+ R.string.battery_detail_switch_title);
+ ((TextView) mCurrentView.findViewById(android.R.id.summary)).setText(
+ R.string.battery_detail_switch_summary);
+ mCurrentView.setClickable(true);
+ mCurrentView.findViewById(android.R.id.icon).setVisibility(View.VISIBLE);
+ mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.VISIBLE);
+ mCurrentView.setOnClickListener(this);
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ mBatteryController.setPowerSaveMode(!mPowerSave);
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ // No toggle state.
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_BATTERY_DETAIL;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index cfc09a0..3750290 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -165,8 +165,8 @@
private QSDetailItems mItems;
@Override
- public int getTitle() {
- return R.string.quick_settings_bluetooth_label;
+ public CharSequence getTitle() {
+ return mContext.getString(R.string.quick_settings_bluetooth_label);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index a8e139c..de4c21c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -160,8 +160,8 @@
private QSDetailItems mItems;
@Override
- public int getTitle() {
- return R.string.quick_settings_cast_title;
+ public CharSequence getTitle() {
+ return mContext.getString(R.string.quick_settings_cast_title);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 6c7b337..c1dcfea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -223,8 +223,8 @@
private final class CellularDetailAdapter implements DetailAdapter {
@Override
- public int getTitle() {
- return R.string.quick_settings_cellular_detail_title;
+ public CharSequence getTitle() {
+ return mContext.getString(R.string.quick_settings_cellular_detail_title);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 4f9f46d..4d9b266 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -218,8 +218,8 @@
private final class DndDetailAdapter implements DetailAdapter, OnAttachStateChangeListener {
@Override
- public int getTitle() {
- return R.string.quick_settings_dnd_label;
+ public CharSequence getTitle() {
+ return mContext.getString(R.string.quick_settings_dnd_label);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 48b4096..95ea3f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -240,8 +240,8 @@
private AccessPoint[] mAccessPoints;
@Override
- public int getTitle() {
- return R.string.quick_settings_wifi_label;
+ public CharSequence getTitle() {
+ return mContext.getString(R.string.quick_settings_wifi_label);
}
public Intent getSettingsIntent() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index b81c23a..2baefd5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -205,7 +206,7 @@
public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
- if (!isDeviceProvisioned()) {
+ if (!isUserSetup()) {
return;
}
@@ -242,7 +243,7 @@
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
- if (!isDeviceProvisioned()) {
+ if (!isUserSetup()) {
return;
}
@@ -277,7 +278,7 @@
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
- if (!isDeviceProvisioned()) {
+ if (!isUserSetup()) {
return;
}
@@ -312,7 +313,7 @@
public void preloadRecents() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
- if (!isDeviceProvisioned()) {
+ if (!isUserSetup()) {
return;
}
@@ -340,7 +341,7 @@
public void cancelPreloadingRecents() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
- if (!isDeviceProvisioned()) {
+ if (!isUserSetup()) {
return;
}
@@ -365,11 +366,20 @@
}
@Override
- public void dockTopTask(boolean draggingInRecents, int stackCreateMode, Rect initialBounds) {
- mImpl.dockTopTask(draggingInRecents, stackCreateMode,initialBounds);
- if (draggingInRecents) {
- mDraggingInRecentsCurrentUser = sSystemServicesProxy.getCurrentUser();
+ public boolean dockTopTask(boolean draggingInRecents, int stackCreateMode, Rect initialBounds) {
+ // Ensure the device has been provisioned before allowing the user to interact with
+ // recents
+ if (!isUserSetup()) {
+ return false;
}
+
+ if (mImpl.dockTopTask(draggingInRecents, stackCreateMode,initialBounds)) {
+ if (draggingInRecents) {
+ mDraggingInRecentsCurrentUser = sSystemServicesProxy.getCurrentUser();
+ }
+ return true;
+ }
+ return false;
}
@Override
@@ -422,7 +432,7 @@
public void showNextAffiliatedTask() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
- if (!isDeviceProvisioned()) {
+ if (!isUserSetup()) {
return;
}
@@ -433,7 +443,7 @@
public void showPrevAffiliatedTask() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
- if (!isDeviceProvisioned()) {
+ if (!isUserSetup()) {
return;
}
@@ -559,11 +569,12 @@
}
/**
- * @return whether this device is provisioned.
+ * @return whether this device is provisioned and the current user is set up.
*/
- private boolean isDeviceProvisioned() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ private boolean isUserSetup() {
+ ContentResolver cr = mContext.getContentResolver();
+ return (Settings.Global.getInt(cr, Settings.Global.DEVICE_PROVISIONED, 0) != 0) &&
+ (Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 213018a..ddeb8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -76,8 +76,6 @@
ActivityOptions.OnAnimationFinishedListener {
private final static String TAG = "RecentsImpl";
- private final static boolean DEBUG = false;
-
// The minimum amount of time between each recents button press that we will handle
private final static int MIN_TOGGLE_DELAY_MS = 350;
// The duration within which the user releasing the alt tab (from when they pressed alt tab)
@@ -186,8 +184,6 @@
mContext = context;
mHandler = new Handler();
mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID);
- Resources res = mContext.getResources();
- LayoutInflater inflater = LayoutInflater.from(mContext);
// Initialize the static foreground thread
ForegroundThread.get();
@@ -198,14 +194,8 @@
ssp.registerTaskStackListener(mTaskStackListener);
// Initialize the static configuration resources
- mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
- mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
- mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
- mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
- mDummyStackView = new TaskStackView(mContext, new TaskStack());
- mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
- null, false);
- reloadHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
+ reloadHeaderBarLayout();
+ updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
// When we start, preload the data associated with the previous recent tasks.
// We can use a new plan since the caches will be the same.
@@ -221,11 +211,12 @@
public void onBootCompleted() {
mBootCompleted = true;
- reloadHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
+ updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
}
- @Override
public void onConfigurationChanged() {
+ reloadHeaderBarLayout();
+ updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
// Don't reuse task stack views if the configuration changes
mCanReuseTaskStackViews = false;
Recents.getConfiguration().updateOnConfigurationChange();
@@ -257,7 +248,6 @@
}
}
- @Override
public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
boolean animate, boolean reloadTasks) {
mTriggeredFromAltTab = triggeredFromAltTab;
@@ -300,7 +290,6 @@
}
}
- @Override
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (mBootCompleted) {
if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
@@ -321,7 +310,6 @@
}
}
- @Override
public void toggleRecents() {
// Skip this toggle if we are already waiting to trigger recents via alt-tab
if (mFastAltTabTrigger.isDozing()) {
@@ -375,7 +363,6 @@
}
}
- @Override
public void preloadRecents() {
// Preload only the raw task list into a new load plan (which will be consumed by the
// RecentsActivity) only if there is a task to animate to.
@@ -396,17 +383,14 @@
}
}
- @Override
public void cancelPreloadingRecents() {
// Do nothing
}
- @Override
public void onDraggingInRecents(float distanceFromTop) {
EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
}
- @Override
public void onDraggingInRecentsEnded(float velocity) {
EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
}
@@ -547,14 +531,18 @@
showRelativeAffiliatedTask(false);
}
- public void dockTopTask(boolean draggingInRecents, int stackCreateMode, Rect initialBounds) {
+ public boolean dockTopTask(boolean draggingInRecents, int stackCreateMode, Rect initialBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
- if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
+ boolean screenPinningActive = ssp.isScreenPinningActive();
+ boolean isTopTaskHome = SystemServicesProxy.isHomeStack(topTask.stackId);
+ if (topTask != null && !isTopTaskHome && !screenPinningActive) {
ssp.moveTaskToDockedStack(topTask.id, stackCreateMode, initialBounds);
showRecents(false /* triggeredFromAltTab */, draggingInRecents, false /* animate */,
true /* reloadTasks*/);
+ return true;
}
+ return false;
}
/**
@@ -567,6 +555,26 @@
}
/**
+ * Reloads all the layouts for the header bar transition.
+ */
+ private void reloadHeaderBarLayout() {
+ Resources res = mContext.getResources();
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+
+ mStatusBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mNavBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height);
+ mNavBarWidth = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_width);
+ mTaskBarHeight = res.getDimensionPixelSize(
+ R.dimen.recents_task_bar_height);
+ mDummyStackView = new TaskStackView(mContext, new TaskStack());
+ mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
+ null, false);
+ }
+
+ /**
* Prepares the header bar layout for the next transition, if the task view bounds has changed
* since the last call, it will attempt to re-measure and layout the header bar to the new size.
*
@@ -574,7 +582,8 @@
* is not already bound (can be expensive)
* @param stack the stack to initialize the stack layout with
*/
- private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget, TaskStack stack) {
+ private void updateHeaderBarLayout(boolean tryAndBindSearchWidget,
+ TaskStack stack) {
RecentsConfiguration config = Recents.getConfiguration();
SystemServicesProxy ssp = Recents.getSystemServices();
Rect windowRect = ssp.getWindowRect();
@@ -639,7 +648,7 @@
preloadIcon(topTask);
// Update the header bar if necessary
- reloadHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
+ updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
// Update the destination rect
mDummyStackView.updateLayoutForStack(stack);
@@ -825,7 +834,7 @@
TaskStack stack = sInstanceLoadPlan.getTaskStack();
// Update the header bar if necessary
- reloadHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
+ updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
// Prepare the dummy stack for the transition
mDummyStackView.updateLayoutForStack(stack);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index dfcf41bc..3406da9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -506,7 +506,7 @@
public void sendCloseSystemWindows(String reason) {
if (ActivityManagerNative.isSystemReady()) {
try {
- ActivityManagerNative.getDefault().closeSystemDialogs(reason);
+ mIam.closeSystemDialogs(reason);
} catch (RemoteException e) {
}
}
@@ -779,6 +779,19 @@
}
/**
+ * Returns whether the current task is in screen-pinning mode.
+ */
+ public boolean isScreenPinningActive() {
+ if (mIam == null) return false;
+
+ try {
+ return mIam.isInLockTaskMode();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
* Returns a global setting.
*/
public int getGlobalSetting(Context context, String setting) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 086fb58..2bf2ccb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -88,15 +88,6 @@
}
/**
- * Cancels an animation.
- */
- public static void cancelAnimation(Animator animator) {
- if (animator != null) {
- animator.cancel();
- }
- }
-
- /**
* Cancels an animation ensuring that if it has listeners, onCancel and onEnd
* are not called.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index fce916b..890713e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -147,27 +147,11 @@
public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
TaskStackLayoutAlgorithm stackLayout) {
if (mTaskRectMap.containsKey(task.key)) {
- final Rect taskRect = stackLayout.mTaskRect;
final RectF ffRect = mTaskRectMap.get(task.key);
transformOut.scale = 1f;
transformOut.alpha = 1f;
transformOut.translationZ = stackLayout.mMaxTranslationZ;
- if (task.thumbnail != null) {
- if (task.bounds == null) {
- // This is a stack task that has no freeform thumbnail, so keep the same bitmap
- // scale as it had in the stack
- transformOut.thumbnailScale = (float) taskRect.width() /
- task.thumbnail.getWidth();
- } else {
- // This is a freeform rect so fit the bitmap to the task bounds
- transformOut.thumbnailScale = Math.min(
- ffRect.width() / task.thumbnail.getWidth(),
- ffRect.height() / task.thumbnail.getHeight());
- }
- } else {
- transformOut.thumbnailScale = 1f;
- }
transformOut.rect.set(ffRect);
transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top);
transformOut.visible = true;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 726e453..c2bb745 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -592,12 +592,8 @@
transformOut.reset();
return transformOut;
}
- getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
+ return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
frontTransform);
- if (task.thumbnail != null) {
- transformOut.thumbnailScale = (float) mTaskRect.width() / task.thumbnail.getWidth();
- }
- return transformOut;
}
}
@@ -661,7 +657,6 @@
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = (transformOut.rect.top < mStackRect.bottom) &&
(frontTransform == null || transformOut.rect.top != frontTransform.rect.top);
- transformOut.thumbnailScale = 1f;
transformOut.p = relP;
return transformOut;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index e8652f5..bc441b2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -200,8 +200,15 @@
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
- mHeaderView.onTaskViewSizeChanged(w, h);
- mThumbnailView.onTaskViewSizeChanged(w, h);
+ if (w > 0 && h > 0) {
+ mHeaderView.onTaskViewSizeChanged(w, h);
+ mThumbnailView.onTaskViewSizeChanged(w, h);
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
}
@Override
@@ -244,24 +251,17 @@
void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
TaskViewAnimation toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
RecentsConfiguration config = Recents.getConfiguration();
- Utilities.cancelAnimation(mTransformAnimation);
+ Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
// Compose the animations for the transform
mTmpAnimators.clear();
- boolean requiresHwLayers = toTransform.applyToTaskView(this, mTmpAnimators, toAnimation,
- !config.fakeShadows);
+ toTransform.applyToTaskView(this, mTmpAnimators, toAnimation, !config.fakeShadows);
if (toAnimation.isImmediate()) {
- mThumbnailView.setBitmapScale(toTransform.thumbnailScale);
setTaskProgress(toTransform.p);
if (toAnimation.listener != null) {
toAnimation.listener.onAnimationEnd(null);
}
} else {
- if (Float.compare(mThumbnailView.getBitmapScale(), toTransform.thumbnailScale) != 0) {
- mTmpAnimators.add(ObjectAnimator.ofFloat(mThumbnailView,
- TaskViewThumbnail.BITMAP_SCALE, mThumbnailView.getBitmapScale(),
- toTransform.thumbnailScale));
- }
if (Float.compare(getTaskProgress(), toTransform.p) != 0) {
mTmpAnimators.add(ObjectAnimator.ofFloat(this, TASK_PROGRESS, getTaskProgress(),
toTransform.p));
@@ -272,22 +272,13 @@
// Create the animator
mTransformAnimation = toAnimation.createAnimator(mTmpAnimators);
- if (requiresHwLayers) {
- setLayerType(View.LAYER_TYPE_HARDWARE, null);
- mTransformAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setLayerType(View.LAYER_TYPE_NONE, null);
- }
- });
- }
mTransformAnimation.start();
}
}
/** Resets this view's properties */
void resetViewProperties() {
- Utilities.cancelAnimation(mTransformAnimation);
+ Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
setDim(0);
setVisibility(View.VISIBLE);
getViewBounds().reset();
@@ -303,7 +294,7 @@
* Cancels any current transform animations.
*/
public void cancelTransformAnimation() {
- Utilities.cancelAnimation(mTransformAnimation);
+ Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
}
/** Enables/disables handling touch on this task view. */
@@ -389,7 +380,7 @@
} else {
float dimAlpha = mDimAlpha / 255.0f;
mThumbnailView.setDimAlpha(dimAlpha);
- mHeaderView.setDimAlpha(dim);
+ mHeaderView.setDimAlpha(dimAlpha);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index e8b7574..6a47424 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,18 +16,18 @@
package com.android.systemui.recents.views;
+import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorFilter;
import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
+import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnimationUtils;
@@ -36,6 +36,7 @@
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
+
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
@@ -55,6 +56,66 @@
public class TaskViewHeader extends FrameLayout
implements View.OnClickListener, View.OnLongClickListener {
+ private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.125f;
+
+ /**
+ * A color drawable that draws a slight highlight at the top to help it stand out.
+ */
+ private class HighlightColorDrawable extends Drawable {
+
+ private Paint mHighlightPaint = new Paint();
+ private Paint mBackgroundPaint = new Paint();
+
+ private float[] mTmpHSL = new float[3];
+
+ public HighlightColorDrawable() {
+ mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
+ mBackgroundPaint.setAntiAlias(true);
+ mHighlightPaint.setColor(Color.argb(255, 255, 255, 255));
+ mHighlightPaint.setAntiAlias(true);
+ }
+
+ public void setColorAndDim(int color, float dimAlpha) {
+ mBackgroundPaint.setColor(color);
+
+ ColorUtils.colorToHSL(color, mTmpHSL);
+ // TODO: Consider using the saturation of the color to adjust the lightness as well
+ mTmpHSL[2] = Math.min(1f,
+ mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
+ mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL));
+
+ invalidateSelf();
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ // Do nothing
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ // Do nothing
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // Draw the highlight at the top edge (but put the bottom edge just out of view)
+ canvas.drawRoundRect(0, 0, mTaskViewRect.width(),
+ 2 * Math.max(mHighlightHeight, mCornerRadius),
+ mCornerRadius, mCornerRadius, mHighlightPaint);
+
+ // Draw the background with the rounded corners
+ canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(),
+ getHeight() + mCornerRadius,
+ mCornerRadius, mCornerRadius, mBackgroundPaint);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+ }
+
Task mTask;
// Header views
@@ -68,17 +129,18 @@
Rect mTaskViewRect = new Rect();
int mCornerRadius;
int mHighlightHeight;
+ float mDimAlpha;
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
- RippleDrawable mBackground;
- GradientDrawable mBackgroundColorDrawable;
+ int mTaskBarViewLightTextColor;
+ int mTaskBarViewDarkTextColor;
String mDismissContentDescription;
- // Static highlight that we draw at the top of each view
- static Paint sHighlightPaint;
+ // Header background
+ private HighlightColorDrawable mBackground;
// Header dim, which is only used when task view hardware layers are not used
- Paint mDimLayerPaint = new Paint();
+ private Paint mDimLayerPaint = new Paint();
Interpolator mFastOutSlowInInterpolator;
Interpolator mFastOutLinearInInterpolator;
@@ -100,29 +162,26 @@
setWillNotDraw(false);
// Load the dismiss resources
- mDimLayerPaint.setColor(Color.argb(0, 0, 0, 0));
+ Resources res = context.getResources();
mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
- mDismissContentDescription =
- context.getString(R.string.accessibility_recents_item_will_be_dismissed);
- mCornerRadius = getResources().getDimensionPixelSize(
- R.dimen.recents_task_view_rounded_corners_radius);
- mHighlightHeight = getResources().getDimensionPixelSize(
- R.dimen.recents_task_view_highlight);
+ mDismissContentDescription = context.getString(
+ R.string.accessibility_recents_item_will_be_dismissed);
+ mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
+ mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
+ mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
+ mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_linear_in);
- // Configure the highlight paint
- if (sHighlightPaint == null) {
- sHighlightPaint = new Paint();
- sHighlightPaint.setStyle(Paint.Style.STROKE);
- sHighlightPaint.setStrokeWidth(mHighlightHeight);
- sHighlightPaint.setColor(context.getColor(R.color.recents_task_bar_highlight_color));
- sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
- sHighlightPaint.setAntiAlias(true);
- }
+ // Configure the background and dim
+ mBackground = new HighlightColorDrawable();
+ mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
+ setBackground(mBackground);
+ mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
+ mDimLayerPaint.setAntiAlias(true);
}
@Override
@@ -139,16 +198,6 @@
if (mIconView.getBackground() instanceof RippleDrawable) {
mIconView.setBackground(null);
}
-
- mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(
- R.drawable.recents_task_view_header_bg_color);
- // Copy the ripple drawable since we are going to be manipulating it
- mBackground = (RippleDrawable)
- getContext().getDrawable(R.drawable.recents_task_view_header_bg);
- mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
- mBackground.setColor(ColorStateList.valueOf(0));
- mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable);
- setBackground(mBackground);
}
/**
@@ -156,6 +205,11 @@
* to match the frame changes.
*/
public void onTaskViewSizeChanged(int width, int height) {
+ // Return early if the bounds have not changed
+ if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) {
+ return;
+ }
+
mTaskViewRect.set(0, 0, width, height);
boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE;
int appIconWidth = mIconView.getMeasuredWidth();
@@ -201,31 +255,39 @@
}
@Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height());
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || (who == mBackground);
}
@Override
- protected void onDraw(Canvas canvas) {
- // Draw the highlight at the top edge (but put the bottom edge just out of view)
- float offset = (float) Math.ceil(mHighlightHeight / 2f);
- float radius = mCornerRadius;
- int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
- canvas.clipRect(0, 0, mTaskViewRect.width(), getMeasuredHeight());
- canvas.drawRoundRect(-offset, 0f, (float) mTaskViewRect.width() + offset,
- getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
- canvas.restoreToCount(count);
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+
+ // Draw the dim layer with the rounded corners
+ canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
+ mCornerRadius, mCornerRadius, mDimLayerPaint);
}
/**
* Sets the dim alpha, only used when we are not using hardware layers.
* (see RecentsConfiguration.useHardwareLayers)
*/
- void setDimAlpha(int alpha) {
- mDimLayerPaint.setColor(Color.argb(alpha, 0, 0, 0));
+ void setDimAlpha(float dimAlpha) {
+ mDimAlpha = dimAlpha;
+ updateBackgroundColor(dimAlpha);
invalidate();
}
+ /**
+ * Updates the background and highlight colors for this header.
+ */
+ private void updateBackgroundColor(float dimAlpha) {
+ if (mTask != null) {
+ mBackground.setColorAndDim(mTask.colorPrimary, dimAlpha);
+ mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
+ }
+ }
+
/** Binds the bar view to the task */
public void rebindToTask(Task t) {
SystemServicesProxy ssp = Recents.getSystemServices();
@@ -233,6 +295,7 @@
// If an activity icon is defined, then we use that as the primary icon to show in the bar,
// otherwise, we fall back to the application icon
+ updateBackgroundColor(mDimAlpha);
if (t.icon != null) {
mIconView.setImageDrawable(t.icon);
}
@@ -240,20 +303,8 @@
mTitleView.setText(t.title);
}
mTitleView.setContentDescription(t.contentDescription);
-
- // Try and apply the system ui tint
- int existingBgColor = (getBackground() instanceof ColorDrawable) ?
- ((ColorDrawable) getBackground()).getColor() : 0;
- if (existingBgColor != t.colorPrimary) {
- mBackgroundColorDrawable.setColor(t.colorPrimary);
- }
-
- int taskBarViewLightTextColor = getResources().getColor(
- R.color.recents_task_bar_light_text_color);
- int taskBarViewDarkTextColor = getResources().getColor(
- R.color.recents_task_bar_dark_text_color);
mTitleView.setTextColor(t.useLightOnPrimaryColor ?
- taskBarViewLightTextColor : taskBarViewDarkTextColor);
+ mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
mLightDismissDrawable : mDarkDismissDrawable);
mDismissButton.setContentDescription(String.format(mDismissContentDescription,
@@ -273,7 +324,9 @@
? R.drawable.recents_move_task_freeform_light
: R.drawable.recents_move_task_freeform_dark);
}
- mMoveTaskButton.setVisibility(View.VISIBLE);
+ if (mMoveTaskButton.getVisibility() != View.VISIBLE) {
+ mMoveTaskButton.setVisibility(View.VISIBLE);
+ }
mMoveTaskButton.setOnClickListener(this);
}
@@ -329,15 +382,6 @@
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- // Draw the dim layer with the rounded corners
- canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight(),
- mCornerRadius, mCornerRadius, mDimLayerPaint);
- }
-
- @Override
public void onClick(View v) {
if (v == mIconView) {
// In accessibility, a single click on the focused app info button will show it
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 8edfae0..39d0604 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -42,26 +42,15 @@
*/
public class TaskViewThumbnail extends View {
- public static final Property<TaskViewThumbnail, Float> BITMAP_SCALE =
- new FloatProperty<TaskViewThumbnail>("bitmapScale") {
- @Override
- public void setValue(TaskViewThumbnail object, float scale) {
- object.setBitmapScale(scale);
- }
-
- @Override
- public Float get(TaskViewThumbnail object) {
- return object.getBitmapScale();
- }
- };
+ private Task mTask;
// Drawing
+ Rect mThumbnailRect = new Rect();
Rect mTaskViewRect = new Rect();
int mCornerRadius;
float mDimAlpha;
Matrix mScaleMatrix = new Matrix();
Paint mDrawPaint = new Paint();
- float mBitmapScale = 1f;
BitmapShader mBitmapShader;
LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
@@ -104,7 +93,13 @@
* to match the frame changes.
*/
public void onTaskViewSizeChanged(int width, int height) {
+ // Return early if the bounds have not changed
+ if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) {
+ return;
+ }
+
mTaskViewRect.set(0, 0, width, height);
+ updateThumbnailScale();
invalidate();
}
@@ -125,9 +120,12 @@
mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
mDrawPaint.setShader(mBitmapShader);
+ mThumbnailRect.set(0, 0, bm.getWidth(), bm.getHeight());
+ updateThumbnailScale();
} else {
mBitmapShader = null;
mDrawPaint.setShader(null);
+ mThumbnailRect.setEmpty();
}
invalidate();
}
@@ -151,12 +149,23 @@
}
/**
- * Sets the scale of the bitmap relative to this view.
+ * Updates the scale of the bitmap relative to this view.
*/
- public void setBitmapScale(float scale) {
+ public void updateThumbnailScale() {
if (mBitmapShader != null) {
- mBitmapScale = scale;
- mScaleMatrix.setScale(mBitmapScale, mBitmapScale);
+ float thumbnailScale;
+ if (!mTask.isFreeformTask() || mTask.bounds == null) {
+ // If this is a stack task, or a stack task moved into the freeform workspace, then
+ // just scale this thumbnail to fit the width of the view
+ thumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width();
+ } else {
+ // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail
+ // to fit the entire bitmap into the task bounds
+ thumbnailScale = Math.min(
+ (float) mTaskViewRect.width() / mThumbnailRect.width(),
+ (float) mTaskViewRect.height() / mThumbnailRect.height());
+ }
+ mScaleMatrix.setScale(thumbnailScale, thumbnailScale);
mBitmapShader.setLocalMatrix(mScaleMatrix);
}
if (!mInvisible) {
@@ -164,10 +173,6 @@
}
}
- public float getBitmapScale() {
- return mBitmapScale;
- }
-
/** Updates the clip rect based on the given task bar. */
void updateClipToTaskBar(View taskBar) {
mTaskBar = taskBar;
@@ -200,6 +205,7 @@
/** Binds the thumbnail view to the task */
void rebindToTask(Task t) {
+ mTask = t;
if (t.thumbnail != null) {
setThumbnail(t.thumbnail);
} else {
@@ -209,6 +215,7 @@
/** Unbinds the thumbnail view from the task */
void unbindFromTask() {
+ mTask = null;
setThumbnail(null);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 14bab64..538c248 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -86,7 +86,6 @@
public float translationZ = 0;
public float scale = 1f;
public float alpha = 1f;
- public float thumbnailScale = 1f;
public boolean visible = false;
float p = 0f;
@@ -101,7 +100,6 @@
translationZ = 0;
scale = 1f;
alpha = 1f;
- thumbnailScale = 1f;
visible = false;
rect.setEmpty();
p = 0f;
@@ -127,15 +125,12 @@
/**
* Applies this transform to a view.
- *
- * @return whether hardware layers are required for this animation.
*/
- public boolean applyToTaskView(TaskView v, ArrayList<Animator> animators,
+ public void applyToTaskView(TaskView v, ArrayList<Animator> animators,
TaskViewAnimation taskAnimation, boolean allowShadows) {
// Return early if not visible
- boolean requiresHwLayers = false;
if (!visible) {
- return requiresHwLayers;
+ return;
}
if (taskAnimation.isImmediate()) {
@@ -165,7 +160,6 @@
}
if (hasAlphaChangedFrom(v.getAlpha())) {
animators.add(ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha));
- requiresHwLayers = true;
}
if (hasRectChangedFrom(v)) {
animators.add(ObjectAnimator.ofPropertyValuesHolder(v,
@@ -175,7 +169,6 @@
PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom)));
}
}
- return requiresHwLayers;
}
/** Reset the transform on a view. */
@@ -188,6 +181,5 @@
v.setAlpha(1f);
v.getViewBounds().setClipBottom(0);
v.setLeftTopRightBottom(0, 0, 0, 0);
- v.mThumbnailView.setBitmapScale(1f);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 109cf47..824d10a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -28,7 +28,6 @@
import android.graphics.Region.Op;
import android.hardware.display.DisplayManager;
import android.util.AttributeSet;
-import android.util.MathUtils;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
@@ -39,7 +38,6 @@
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
@@ -48,8 +46,10 @@
import android.widget.FrameLayout;
import android.widget.ImageButton;
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DockedDividerUtils;
import com.android.systemui.R;
-import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.statusbar.FlingAnimationUtils;
import static android.view.PointerIcon.STYLE_HORIZONTAL_DOUBLE_ARROW;
@@ -167,7 +167,8 @@
public boolean startDragging(boolean animate) {
mHandle.setTouching(true, animate);
mDockSide = mWindowManagerProxy.getDockSide();
- mSnapAlgorithm = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils, mDisplayWidth,
+ mSnapAlgorithm = new DividerSnapAlgorithm(getContext().getResources(),
+ mFlingAnimationUtils.getMinVelocityPxPerSecond(), mDisplayWidth,
mDisplayHeight, mDividerSize, isHorizontalDivision(), mStableInsets);
if (mDockSide != WindowManager.DOCKED_INVALID) {
mWindowManagerProxy.setResizing(true);
@@ -362,36 +363,6 @@
return mStartPosition + touchY - mStartY;
}
- public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
- outRect.set(0, 0, mDisplayWidth, mDisplayHeight);
- switch (dockSide) {
- case WindowManager.DOCKED_LEFT:
- outRect.right = position;
- break;
- case WindowManager.DOCKED_TOP:
- outRect.bottom = position;
- break;
- case WindowManager.DOCKED_RIGHT:
- outRect.left = position + mDividerWindowWidth - 2 * mDividerInsets;
- break;
- case WindowManager.DOCKED_BOTTOM:
- outRect.top = position + mDividerWindowWidth - 2 * mDividerInsets;
- break;
- }
- if (outRect.left > outRect.right) {
- outRect.left = outRect.right;
- }
- if (outRect.top > outRect.bottom) {
- outRect.top = outRect.bottom;
- }
- if (outRect.right < outRect.left) {
- outRect.right = outRect.left;
- }
- if (outRect.bottom < outRect.top) {
- outRect.bottom = outRect.top;
- }
- }
-
private int invertDockSide(int dockSide) {
switch (dockSide) {
case WindowManager.DOCKED_LEFT:
@@ -421,6 +392,11 @@
containingRect.right, containingRect.bottom);
}
+ public void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
+ DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect, mDisplayWidth,
+ mDisplayHeight, mDividerSize);
+ }
+
public void resizeStack(int position, int taskPosition, SnapTarget taskSnapTarget) {
calculateBoundsForPosition(position, mDockSide, mDockedRect);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index b93fc76..f41e47b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -166,7 +166,7 @@
}
@Override
- public void onPowerSaveChanged() {
+ public void onPowerSaveChanged(boolean isPowerSave) {
// could not care less
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index cc85d0f..e5b4f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -29,8 +29,7 @@
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.stackdivider.Divider;
-import com.android.systemui.stackdivider.DividerSnapAlgorithm.SnapTarget;
-import com.android.systemui.stackdivider.DividerView;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
import com.android.systemui.tuner.TunerService;
import static android.view.WindowManager.*;
@@ -166,16 +165,19 @@
int y = (int) event.getY();
int xDiff = Math.abs(x - mTouchDownX);
int yDiff = Math.abs(y - mTouchDownY);
+ if (mDivider == null || mRecentsComponent == null) {
+ return false;
+ }
if (!mDockWindowTouchSlopExceeded) {
boolean touchSlopExceeded = !mIsVertical
? yDiff > mScrollTouchSlop && yDiff > xDiff
: xDiff > mScrollTouchSlop && xDiff > yDiff;
if (touchSlopExceeded && mDivider.getView().getWindowManagerProxy().getDockSide()
== DOCKED_INVALID) {
- mDragMode = calculateDragMode();
Rect initialBounds = null;
+ int dragMode = calculateDragMode();
int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
- if (mDragMode == DRAG_MODE_DIVIDER) {
+ if (dragMode == DRAG_MODE_DIVIDER) {
initialBounds = new Rect();
mDivider.getView().calculateBoundsForPosition(mIsVertical
? (int) event.getRawX()
@@ -184,19 +186,23 @@
? DOCKED_TOP
: DOCKED_LEFT,
initialBounds);
- } else if (mDragMode == DRAG_MODE_RECENTS && mTouchDownX
+ } else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX
< mContext.getResources().getDisplayMetrics().widthPixels / 2) {
createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
}
- mRecentsComponent.dockTopTask(mDragMode == DRAG_MODE_RECENTS, createMode,
- initialBounds);
- if (mDragMode == DRAG_MODE_DIVIDER) {
- mDivider.getView().startDragging(false /* animate */);
+ boolean docked = mRecentsComponent.dockTopTask(dragMode == DRAG_MODE_RECENTS,
+ createMode, initialBounds);
+ if (docked) {
+ mDragMode = dragMode;
+ if (mDragMode == DRAG_MODE_DIVIDER) {
+ mDivider.getView().startDragging(false /* animate */);
+ }
+ mDockWindowTouchSlopExceeded = true;
+ MetricsLogger.action(mContext,
+ MetricsLogger.ACTION_WINDOW_DOCK_SWIPE);
+
+ return true;
}
- mDockWindowTouchSlopExceeded = true;
- MetricsLogger.action(mContext,
- MetricsLogger.ACTION_WINDOW_DOCK_SWIPE);
- return true;
}
} else {
if (mDragMode == DRAG_MODE_DIVIDER) {
@@ -214,7 +220,7 @@
private void handleDragActionUpEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000);
- if (mDockWindowTouchSlopExceeded) {
+ if (mDockWindowTouchSlopExceeded && mDivider != null && mRecentsComponent != null) {
if (mDragMode == DRAG_MODE_DIVIDER) {
mDivider.getView().stopDragging(mIsVertical
? (int) event.getRawX()
@@ -254,7 +260,7 @@
float absVelY = Math.abs(velocityY);
boolean isValidFling = absVelX > mMinFlingVelocity &&
mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
- if (isValidFling) {
+ if (isValidFling && mRecentsComponent != null) {
boolean showNext;
if (!mIsRTL) {
showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 6aa072f..f2c57e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -23,8 +23,6 @@
import android.view.View;
import android.widget.FrameLayout;
-import java.util.ArrayList;
-
public abstract class PanelBar extends FrameLayout {
public static final boolean DEBUG = false;
public static final String TAG = PanelBar.class.getSimpleName();
@@ -39,14 +37,10 @@
public static final int STATE_OPENING = 1;
public static final int STATE_OPEN = 2;
- PanelHolder mPanelHolder;
- ArrayList<PanelView> mPanels = new ArrayList<PanelView>();
- PanelView mTouchingPanel;
+ PanelView mPanel;
private int mState = STATE_CLOSED;
private boolean mTracking;
- float mPanelExpandedFractionSum;
-
public void go(int state) {
if (DEBUG) LOG("go state: %d -> %d", mState, state);
mState = state;
@@ -61,54 +55,28 @@
super.onFinishInflate();
}
- public void addPanel(PanelView pv) {
- mPanels.add(pv);
+ public void setPanel(PanelView pv) {
+ mPanel = pv;
pv.setBar(this);
}
- public void setPanelHolder(PanelHolder ph) {
- if (ph == null) {
- Log.e(TAG, "setPanelHolder: null PanelHolder", new Throwable());
- return;
- }
- mPanelHolder = ph;
- final int N = ph.getChildCount();
- for (int i=0; i<N; i++) {
- final View v = ph.getChildAt(i);
- if (v != null && v instanceof PanelView) {
- addPanel((PanelView) v);
- }
- }
- }
-
public void setBouncerShowing(boolean showing) {
int important = showing ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO;
setImportantForAccessibility(important);
- if (mPanelHolder != null) {
- mPanelHolder.setImportantForAccessibility(important);
- }
+ if (mPanel != null) mPanel.setImportantForAccessibility(important);
}
- public float getBarHeight() {
- return getMeasuredHeight();
- }
-
- public PanelView selectPanelForTouch(MotionEvent touch) {
- final int N = mPanels.size();
- return mPanels.get((int)(N * touch.getX() / getMeasuredWidth()));
- }
-
- public boolean panelsEnabled() {
+ public boolean panelEnabled() {
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// Allow subclasses to implement enable/disable semantics
- if (!panelsEnabled()) {
+ if (!panelEnabled()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Log.v(TAG, String.format("onTouch: all panels disabled, ignoring touch at (%d,%d)",
(int) event.getX(), (int) event.getY()));
@@ -116,14 +84,12 @@
return false;
}
- // figure out which panel needs to be talked to here
if (event.getAction() == MotionEvent.ACTION_DOWN) {
- final PanelView panel = selectPanelForTouch(event);
+ final PanelView panel = mPanel;
if (panel == null) {
// panel is not there, so we'll eat the gesture
Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)",
(int) event.getX(), (int) event.getY()));
- mTouchingPanel = null;
return true;
}
boolean enabled = panel.isEnabled();
@@ -134,90 +100,65 @@
Log.v(TAG, String.format(
"onTouch: panel (%s) is disabled, ignoring touch at (%d,%d)",
panel, (int) event.getX(), (int) event.getY()));
- mTouchingPanel = null;
return true;
}
- startOpeningPanel(panel);
}
- final boolean result = mTouchingPanel != null
- ? mTouchingPanel.onTouchEvent(event)
- : true;
- return result;
- }
-
- // called from PanelView when self-expanding, too
- public void startOpeningPanel(PanelView panel) {
- if (DEBUG) LOG("startOpeningPanel: " + panel);
- mTouchingPanel = panel;
- mPanelHolder.setSelectedPanel(mTouchingPanel);
- for (PanelView pv : mPanels) {
- if (pv != panel) {
- pv.collapse(false /* delayed */, 1.0f /* speedUpFactor */);
- }
- }
+ return mPanel == null || mPanel.onTouchEvent(event);
}
public abstract void panelScrimMinFractionChanged(float minFraction);
/**
- * @param panel the panel which changed its expansion state
* @param frac the fraction from the expansion in [0, 1]
* @param expanded whether the panel is currently expanded; this is independent from the
* fraction as the panel also might be expanded if the fraction is 0
*/
- public void panelExpansionChanged(PanelView panel, float frac, boolean expanded) {
+ public void panelExpansionChanged(float frac, boolean expanded) {
boolean fullyClosed = true;
- PanelView fullyOpenedPanel = null;
- if (SPEW) LOG("panelExpansionChanged: start state=%d panel=%s", mState, panel.getName());
- mPanelExpandedFractionSum = 0f;
- for (PanelView pv : mPanels) {
- pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
- // adjust any other panels that may be partially visible
- if (expanded) {
- if (mState == STATE_CLOSED) {
- go(STATE_OPENING);
- onPanelPeeked();
- }
- fullyClosed = false;
- final float thisFrac = pv.getExpandedFraction();
- mPanelExpandedFractionSum += thisFrac;
- if (SPEW) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac);
- if (panel == pv) {
- if (thisFrac == 1f) fullyOpenedPanel = panel;
- }
+ boolean fullyOpened = false;
+ if (SPEW) LOG("panelExpansionChanged: start state=%d", mState);
+ PanelView pv = mPanel;
+ pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
+ // adjust any other panels that may be partially visible
+ if (expanded) {
+ if (mState == STATE_CLOSED) {
+ go(STATE_OPENING);
+ onPanelPeeked();
}
+ fullyClosed = false;
+ final float thisFrac = pv.getExpandedFraction();
+ if (SPEW) LOG("panelExpansionChanged: -> %s: f=%.1f", pv.getName(), thisFrac);
+ fullyOpened = thisFrac >= 1f;
}
- mPanelExpandedFractionSum /= mPanels.size();
- if (fullyOpenedPanel != null && !mTracking) {
+ if (fullyOpened && !mTracking) {
go(STATE_OPEN);
- onPanelFullyOpened(fullyOpenedPanel);
+ onPanelFullyOpened();
} else if (fullyClosed && !mTracking && mState != STATE_CLOSED) {
go(STATE_CLOSED);
- onAllPanelsCollapsed();
+ onPanelCollapsed();
}
if (SPEW) LOG("panelExpansionChanged: end state=%d [%s%s ]", mState,
- (fullyOpenedPanel!=null)?" fullyOpened":"", fullyClosed?" fullyClosed":"");
+ fullyOpened?" fullyOpened":"", fullyClosed?" fullyClosed":"");
}
- public void collapseAllPanels(boolean animate, boolean delayed, float speedUpFactor) {
+ public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
boolean waiting = false;
- for (PanelView pv : mPanels) {
- if (animate && !pv.isFullyCollapsed()) {
- pv.collapse(delayed, speedUpFactor);
- waiting = true;
- } else {
- pv.resetViews();
- pv.setExpandedFraction(0); // just in case
- pv.cancelPeek();
- }
+ PanelView pv = mPanel;
+ if (animate && !pv.isFullyCollapsed()) {
+ pv.collapse(delayed, speedUpFactor);
+ waiting = true;
+ } else {
+ pv.resetViews();
+ pv.setExpandedFraction(0); // just in case
+ pv.cancelPeek();
}
- if (DEBUG) LOG("collapseAllPanels: animate=%s waiting=%s", animate, waiting);
+ if (DEBUG) LOG("collapsePanel: animate=%s waiting=%s", animate, waiting);
if (!waiting && mState != STATE_CLOSED) {
// it's possible that nothing animated, so we replicate the termination
// conditions of panelExpansionChanged here
go(STATE_CLOSED);
- onAllPanelsCollapsed();
+ onPanelCollapsed();
}
}
@@ -225,19 +166,19 @@
if (DEBUG) LOG("onPanelPeeked");
}
- public void onAllPanelsCollapsed() {
- if (DEBUG) LOG("onAllPanelsCollapsed");
+ public void onPanelCollapsed() {
+ if (DEBUG) LOG("onPanelCollapsed");
}
- public void onPanelFullyOpened(PanelView openPanel) {
+ public void onPanelFullyOpened() {
if (DEBUG) LOG("onPanelFullyOpened");
}
- public void onTrackingStarted(PanelView panel) {
+ public void onTrackingStarted() {
mTracking = true;
}
- public void onTrackingStopped(PanelView panel, boolean expand) {
+ public void onTrackingStopped(boolean expand) {
mTracking = false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java
deleted file mode 100644
index 5095ebb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelHolder.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.util.EventLog;
-import android.view.MotionEvent;
-import android.widget.FrameLayout;
-
-import com.android.systemui.EventLogTags;
-
-public class PanelHolder extends FrameLayout {
- public static final boolean DEBUG_GESTURES = true;
-
- private int mSelectedPanelIndex = -1;
-
- public PanelHolder(Context context, AttributeSet attrs) {
- super(context, attrs);
- setChildrenDrawingOrderEnabled(true);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- setChildrenDrawingOrderEnabled(true);
- }
-
- public int getPanelIndex(PanelView pv) {
- final int N = getChildCount();
- for (int i=0; i<N; i++) {
- final PanelView v = (PanelView) getChildAt(i);
- if (pv == v) return i;
- }
- return -1;
- }
-
- public void setSelectedPanel(PanelView pv) {
- mSelectedPanelIndex = getPanelIndex(pv);
- }
-
- @Override
- protected int getChildDrawingOrder(int childCount, int i) {
- if (mSelectedPanelIndex == -1) {
- return i;
- } else {
- if (i == childCount - 1) {
- return mSelectedPanelIndex;
- } else if (i >= mSelectedPanelIndex) {
- return i + 1;
- } else {
- return i;
- }
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (DEBUG_GESTURES) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- EventLog.writeEvent(EventLogTags.SYSUI_PANELHOLDER_TOUCH,
- event.getActionMasked(), (int) event.getX(), (int) event.getY());
- }
- }
- return false;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 7b2498f..aa01bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -432,7 +432,7 @@
protected void onTrackingStopped(boolean expand) {
mTracking = false;
- mBar.onTrackingStopped(PanelView.this, expand);
+ mBar.onTrackingStopped(expand);
notifyBarPanelExpansionChanged();
}
@@ -440,7 +440,7 @@
endClosing();
mTracking = true;
mCollapseAfterPeek = false;
- mBar.onTrackingStarted(PanelView.this);
+ mBar.onTrackingStarted();
notifyExpandingStarted();
notifyBarPanelExpansionChanged();
}
@@ -893,7 +893,6 @@
!= mStatusBar.getStatusBarHeight()) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (animate) {
- mBar.startOpeningPanel(PanelView.this);
notifyExpandingStarted();
fling(0, true /* expand */);
} else {
@@ -1025,7 +1024,7 @@
}
protected void notifyBarPanelExpansionChanged() {
- mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending
+ mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f || mPeekPending
|| mPeekAnimator != null || mInstantExpanding || isPanelVisibleBecauseOfHeadsUp()
|| mTracking || mHeightAnimator != null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d721a77..50e88d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -81,7 +81,6 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
-import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewStub;
@@ -342,7 +341,6 @@
// Tracking finger for opening/closing.
boolean mTracking;
- VelocityTracker mVelocityTracker;
int[] mAbsPos = new int[2];
ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
@@ -692,16 +690,14 @@
}
});
- mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
- mStatusBarView.setBar(this);
-
- PanelHolder holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder);
- mStatusBarView.setPanelHolder(holder);
-
mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
R.id.notification_panel);
mNotificationPanel.setStatusBar(this);
+ mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
+ mStatusBarView.setBar(this);
+ mStatusBarView.setPanel(mNotificationPanel);
+
if (!ActivityManager.isHighEndGfx()) {
mStatusBarWindow.setBackground(null);
mNotificationPanel.setBackground(new FastColorDrawable(context.getColor(
@@ -814,10 +810,10 @@
mBatteryController = new BatteryController(mContext);
mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
@Override
- public void onPowerSaveChanged() {
+ public void onPowerSaveChanged(boolean isPowerSave) {
mHandler.post(mCheckBarModes);
if (mDozeServiceHost != null) {
- mDozeServiceHost.firePowerSaveChanged(mBatteryController.isPowerSave());
+ mDozeServiceHost.firePowerSaveChanged(isPowerSave);
}
}
@Override
@@ -1128,12 +1124,14 @@
@Override
public boolean onLongClick(View v) {
if (mRecents != null) {
- mRecents.dockTopTask(false /* draggingInRecents */,
+ boolean docked = mRecents.dockTopTask(false /* draggingInRecents */,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
null /* initialBounds */);
- MetricsLogger.action(mContext,
- MetricsLogger.ACTION_WINDOW_DOCK_LONGPRESS);
- return true;
+ if (docked) {
+ MetricsLogger.action(mContext,
+ MetricsLogger.ACTION_WINDOW_DOCK_LONGPRESS);
+ return true;
+ }
}
return false;
}
@@ -2275,7 +2273,7 @@
mStatusBarWindowManager.setStatusBarFocusable(false);
mStatusBarWindow.cancelExpandHelper();
- mStatusBarView.collapseAllPanels(true /* animate */, delayed, speedUpFactor);
+ mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
}
}
@@ -2324,7 +2322,7 @@
public void animateCollapseQuickSettings() {
if (mState == StatusBarState.SHADE) {
- mStatusBarView.collapseAllPanels(true, false /* delayed */, 1.0f /* speedUpFactor */);
+ mStatusBarView.collapsePanel(true, false /* delayed */, 1.0f /* speedUpFactor */);
}
}
@@ -2337,7 +2335,7 @@
}
// Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mStatusBarView.collapseAllPanels(/*animate=*/ false, false /* delayed*/,
+ mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/,
1.0f /* speedUpFactor */);
mNotificationPanel.closeQs();
@@ -2429,7 +2427,7 @@
mStatusBarWindowState = state;
if (DEBUG_WINDOW_STATE) Log.d(TAG, "Status bar " + windowStateToString(state));
if (!showing && mState == StatusBarState.SHADE) {
- mStatusBarView.collapseAllPanels(false /* animate */, false /* delayed */,
+ mStatusBarView.collapsePanel(false /* animate */, false /* delayed */,
1.0f /* speedUpFactor */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index ab37e6a..813a167 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
-import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.EventLog;
import android.view.MotionEvent;
@@ -26,7 +25,6 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.EventLogTags;
-import com.android.systemui.R;
public class PhoneStatusBarView extends PanelBar {
private static final String TAG = "PhoneStatusBarView";
@@ -35,8 +33,7 @@
PhoneStatusBar mBar;
- PanelView mLastFullyOpenedPanel = null;
- PanelView mNotificationPanel;
+ boolean mIsFullyOpenedPanel = false;
private final PhoneStatusBarTransitions mBarTransitions;
private ScrimController mScrimController;
private float mMinFraction;
@@ -72,15 +69,7 @@
}
@Override
- public void addPanel(PanelView pv) {
- super.addPanel(pv);
- if (pv.getId() == R.id.notification_panel) {
- mNotificationPanel = pv;
- }
- }
-
- @Override
- public boolean panelsEnabled() {
+ public boolean panelEnabled() {
return mBar.panelsEnabled();
}
@@ -100,24 +89,17 @@
}
@Override
- public PanelView selectPanelForTouch(MotionEvent touch) {
- return mNotificationPanel.getExpandedHeight() > 0
- ? null
- : mNotificationPanel;
- }
-
- @Override
public void onPanelPeeked() {
super.onPanelPeeked();
mBar.makeExpandedVisible(false);
}
@Override
- public void onAllPanelsCollapsed() {
- super.onAllPanelsCollapsed();
+ public void onPanelCollapsed() {
+ super.onPanelCollapsed();
// Close the status bar in the next frame so we can show the end of the animation.
DejankUtils.postAfterTraversal(mHideExpandedRunnable);
- mLastFullyOpenedPanel = null;
+ mIsFullyOpenedPanel = false;
}
public void removePendingHideExpandedRunnables() {
@@ -125,12 +107,12 @@
}
@Override
- public void onPanelFullyOpened(PanelView openPanel) {
- super.onPanelFullyOpened(openPanel);
- if (openPanel != mLastFullyOpenedPanel) {
- openPanel.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ public void onPanelFullyOpened() {
+ super.onPanelFullyOpened();
+ if (!mIsFullyOpenedPanel) {
+ mPanel.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
- mLastFullyOpenedPanel = openPanel;
+ mIsFullyOpenedPanel = true;
}
@Override
@@ -149,8 +131,8 @@
}
@Override
- public void onTrackingStarted(PanelView panel) {
- super.onTrackingStarted(panel);
+ public void onTrackingStarted() {
+ super.onTrackingStarted();
mBar.onTrackingStarted();
mScrimController.onTrackingStarted();
}
@@ -162,8 +144,8 @@
}
@Override
- public void onTrackingStopped(PanelView panel, boolean expand) {
- super.onTrackingStopped(panel, expand);
+ public void onTrackingStopped(boolean expand) {
+ super.onTrackingStopped(expand);
mBar.onTrackingStopped(expand);
}
@@ -187,8 +169,8 @@
}
@Override
- public void panelExpansionChanged(PanelView panel, float frac, boolean expanded) {
- super.panelExpansionChanged(panel, frac, expanded);
+ public void panelExpansionChanged(float frac, boolean expanded) {
+ super.panelExpansionChanged(frac, expanded);
mPanelFraction = frac;
updateScrimFraction();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 3d21f44..d2f1ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -419,7 +419,7 @@
}
@Override
- public void onPowerSaveChanged() {
+ public void onPowerSaveChanged(boolean isPowerSave) {
// could not care less
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 5071df0..bb3e116 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -70,9 +70,14 @@
pw.print(" mPowerSave="); pw.println(mPowerSave);
}
+ public void setPowerSaveMode(boolean powerSave) {
+ mPowerManager.setPowerSaveMode(powerSave);
+ }
+
public void addStateChangedCallback(BatteryStateChangeCallback cb) {
mChangeCallbacks.add(cb);
cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
+ cb.onPowerSaveChanged(mPowerSave);
}
public void removeStateChangedCallback(BatteryStateChangeCallback cb) {
@@ -158,12 +163,12 @@
private void firePowerSaveChanged() {
final int N = mChangeCallbacks.size();
for (int i = 0; i < N; i++) {
- mChangeCallbacks.get(i).onPowerSaveChanged();
+ mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
}
}
public interface BatteryStateChangeCallback {
void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging);
- void onPowerSaveChanged();
+ void onPowerSaveChanged(boolean isPowerSave);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 03409846..4ae0321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -35,25 +35,25 @@
private final ScrimView mScrimBehind;
private final View mBrightnessMirror;
- private final View mPanelHolder;
+ private final View mNotificationPanel;
private final int[] mInt2Cache = new int[2];
public BrightnessMirrorController(StatusBarWindowView statusBarWindow) {
mScrimBehind = (ScrimView) statusBarWindow.findViewById(R.id.scrim_behind);
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
- mPanelHolder = statusBarWindow.findViewById(R.id.panel_holder);
+ mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
}
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mScrimBehind.animateViewAlpha(0.0f, TRANSITION_DURATION_OUT, PhoneStatusBar.ALPHA_OUT);
- outAnimation(mPanelHolder.animate())
+ outAnimation(mNotificationPanel.animate())
.withLayer();
}
public void hideMirror() {
mScrimBehind.animateViewAlpha(1.0f, TRANSITION_DURATION_IN, PhoneStatusBar.ALPHA_IN);
- inAnimation(mPanelHolder.animate())
+ inAnimation(mNotificationPanel.animate())
.withLayer()
.withEndAction(new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index b010761..f3a3554 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -643,8 +643,8 @@
private final Intent USER_SETTINGS_INTENT = new Intent("android.settings.USER_SETTINGS");
@Override
- public int getTitle() {
- return R.string.quick_settings_user_title;
+ public CharSequence getTitle() {
+ return mContext.getString(R.string.quick_settings_user_title);
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 5f6cbf9..232c080 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -69,6 +69,13 @@
*/
static final int FLAG_FEATURE_AUTOCLICK = 0x00000008;
+ /**
+ * Flag for enabling motion event injectsion
+ *
+ * @see #setUserAndEnabledFeatures(int, int)
+ */
+ static final int FLAG_FEATURE_INJECT_MOTION_EVENTS = 0x00000010;
+
private final Runnable mProcessBatchedEventsRunnable = new Runnable() {
@Override
public void run() {
@@ -104,6 +111,8 @@
private MagnificationGestureHandler mMagnificationGestureHandler;
+ private MotionEventInjector mMotionEventInjector;
+
private AutoclickController mAutoclickController;
private KeyboardInterceptor mKeyboardInterceptor;
@@ -191,6 +200,10 @@
}
}
+ public MotionEventInjector getMotionEventInjector() {
+ return mMotionEventInjector;
+ }
+
/**
* Gets current event stream state associated with an input event.
* @return The event stream state that should be used for the event. Null if the event should
@@ -351,6 +364,12 @@
private void enableFeatures() {
resetStreamState();
+ if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
+ mMotionEventInjector = new MotionEventInjector(mContext.getMainLooper());
+ addFirstEventHandler(mMotionEventInjector);
+ mAms.setMotionEventInjector(mMotionEventInjector);
+ }
+
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
mAutoclickController = new AutoclickController(mContext, mUserId);
addFirstEventHandler(mAutoclickController);
@@ -388,6 +407,11 @@
}
private void disableFeatures() {
+ if (mMotionEventInjector != null) {
+ mAms.setMotionEventInjector(null);
+ mMotionEventInjector.onDestroy();
+ mMotionEventInjector = null;
+ }
if (mAutoclickController != null) {
mAutoclickController.onDestroy();
mAutoclickController = null;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8cf25b3..39d5952 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -37,6 +37,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
@@ -72,6 +73,7 @@
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
+import android.view.MotionEvent;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.WindowManagerInternal;
@@ -186,6 +188,8 @@
private KeyEventDispatcher mKeyEventDispatcher;
+ private MotionEventInjector mMotionEventInjector;
+
private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
@@ -779,6 +783,18 @@
}
/**
+ * Called by AccessibilityInputFilter when it creates or destroys the motionEventInjector.
+ * Not using a getter because the AccessibilityInputFilter isn't thread-safe
+ *
+ * @param motionEventInjector The new value of the motionEventInjector. May be null.
+ */
+ void setMotionEventInjector(MotionEventInjector motionEventInjector) {
+ synchronized (mLock) {
+ mMotionEventInjector = motionEventInjector;
+ }
+ }
+
+ /**
* Gets a point within the accessibility focused node where we can send down
* and up events to perform a click.
*
@@ -1273,6 +1289,9 @@
if (userState.mIsAutoclickEnabled) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
}
+ if (userState.mIsPerformGesturesEnabled) {
+ flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
+ }
if (flags != 0) {
if (!mHasInputFilter) {
mHasInputFilter = true;
@@ -1363,6 +1382,7 @@
updateAccessibilityFocusBehaviorLocked(userState);
updateFilterKeyEventsLocked(userState);
updateTouchExplorationLocked(userState);
+ updatePerformGesturesLocked(userState);
updateEnhancedWebAccessibilityLocked(userState);
updateDisplayColorAdjustmentSettingsLocked(userState);
updateMagnificationLocked(userState);
@@ -1451,6 +1471,19 @@
}
}
+ private void updatePerformGesturesLocked(UserState userState) {
+ final int serviceCount = userState.mBoundServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ Service service = userState.mBoundServices.get(i);
+ if ((service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0) {
+ userState.mIsPerformGesturesEnabled = true;
+ return;
+ }
+ }
+ userState.mIsPerformGesturesEnabled = false;
+ }
+
private void updateFilterKeyEventsLocked(UserState userState) {
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
@@ -2564,6 +2597,23 @@
}
@Override
+ public void sendMotionEvents(int sequence, ParceledListSlice events) {
+ synchronized (mLock) {
+ if (mSecurityPolicy.canPerformGestures(this) && (mMotionEventInjector != null)) {
+ mMotionEventInjector.injectEvents((List<MotionEvent>) events.getList(),
+ mServiceInterface, sequence);
+ return;
+ }
+ }
+ try {
+ mServiceInterface.onPerformGestureResult(sequence, false);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending motion event injection failure to "
+ + mServiceInterface, re);
+ }
+ }
+
+ @Override
public boolean performAccessibilityAction(int accessibilityWindowId,
long accessibilityNodeId, int action, Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
@@ -3734,6 +3784,11 @@
& AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
}
+ public boolean canPerformGestures(Service service) {
+ return (service.mAccessibilityServiceInfo.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0;
+ }
+
private int resolveProfileParentLocked(int userId) {
if (userId != mCurrentUserId) {
final long identity = Binder.clearCallingIdentity();
@@ -3878,6 +3933,7 @@
public boolean mIsEnhancedWebAccessibilityEnabled;
public boolean mIsDisplayMagnificationEnabled;
public boolean mIsAutoclickEnabled;
+ public boolean mIsPerformGesturesEnabled;
public boolean mIsFilterKeyEventsEnabled;
public boolean mHasDisplayColorAdjustment;
public boolean mAccessibilityFocusOnlyInActiveWindow;
diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
new file mode 100644
index 0000000..800f0e1
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+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.Slog;
+import android.util.SparseArray;
+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.List;
+
+/**
+ * Injects MotionEvents to permit {@code AccessibilityService}s to touch the screen on behalf of
+ * users.
+ *
+ * All methods except {@code injectEvents} must be called only from the main thread.
+ */
+public class MotionEventInjector implements EventStreamTransformation {
+ 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
+
+ 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 boolean mIsDestroyed = false;
+
+ /**
+ * @param looper A looper on the main thread to use for dispatching new events
+ */
+ public MotionEventInjector(Looper looper) {
+ mHandler = new Handler(looper, new Callback());
+ }
+
+ /**
+ * 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.
+ *
+ * @param events The events to inject. Must all be from the same source.
+ * @param serviceInterface The interface to call back with a result when the gesture is
+ * either complete or cancelled.
+ */
+ public void injectEvents(List<MotionEvent> events,
+ IAccessibilityServiceClient serviceInterface, int sequence) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = events;
+ args.arg2 = serviceInterface;
+ args.argi1 = sequence;
+ mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_INJECT_EVENTS, args));
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ cancelAnyPendingInjectedEvents();
+ sendMotionEventToNext(event, rawEvent, policyFlags);
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent event, int policyFlags) {
+ if (mNext != null) {
+ mNext.onKeyEvent(event, policyFlags);
+ }
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ if (mNext != null) {
+ mNext.onAccessibilityEvent(event);
+ }
+ }
+
+ @Override
+ public void setNext(EventStreamTransformation next) {
+ mNext = next;
+ }
+
+ @Override
+ public void clearEvents(int inputSource) {
+ /*
+ * Reset state for motion events passing through so we won't send a cancel event for
+ * them.
+ */
+ if (!mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
+ mOpenGesturesInProgress.put(inputSource, false);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ cancelAnyPendingInjectedEvents();
+ mIsDestroyed = true;
+ }
+
+ private void injectEventsMainThread(List<MotionEvent> events,
+ IAccessibilityServiceClient serviceInterface, int sequence) {
+ if (mIsDestroyed) {
+ try {
+ serviceInterface.onPerformGestureResult(sequence, false);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending status with mIsDestroyed to " + serviceInterface,
+ re);
+ }
+ return;
+ }
+ cancelAnyPendingInjectedEvents();
+ mSourceOfInjectedGesture = events.get(0).getSource();
+ cancelAnyGestureInProgress(mSourceOfInjectedGesture);
+ mServiceInterfaceForCurrentGesture = serviceInterface;
+ mSequenceForCurrentGesture = sequence;
+ if (mNext == null) {
+ notifyService(false);
+ return;
+ }
+
+ long startTime = SystemClock.uptimeMillis();
+ 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());
+ }
+ }
+
+ private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent,
+ int policyFlags) {
+ if (mNext != null) {
+ mNext.onMotionEvent(event, rawEvent, policyFlags);
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mOpenGesturesInProgress.put(event.getSource(), true);
+ }
+ if ((event.getActionMasked() == MotionEvent.ACTION_UP)
+ || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
+ mOpenGesturesInProgress.put(event.getSource(), false);
+ }
+ }
+ }
+
+ private void cancelAnyGestureInProgress(int source) {
+ 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);
+ sendMotionEventToNext(cancelEvent, cancelEvent,
+ WindowManagerPolicy.FLAG_PASS_TO_USER);
+ }
+ }
+
+ private void cancelAnyPendingInjectedEvents() {
+ if (mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
+ cancelAnyGestureInProgress(mSourceOfInjectedGesture);
+ mHandler.removeMessages(MESSAGE_SEND_MOTION_EVENT);
+ notifyService(false);
+ }
+
+ }
+
+ private void notifyService(boolean success) {
+ try {
+ mServiceInterfaceForCurrentGesture.onPerformGestureResult(
+ mSequenceForCurrentGesture, success);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending motion event injection status to "
+ + mServiceInterfaceForCurrentGesture, re);
+ }
+ }
+
+ private class Callback implements Handler.Callback {
+ @Override
+ public boolean handleMessage(Message message) {
+ if (message.what == MESSAGE_INJECT_EVENTS) {
+ SomeArgs args = (SomeArgs) message.obj;
+ injectEventsMainThread((List<MotionEvent>) args.arg1,
+ (IAccessibilityServiceClient) args.arg2, args.argi1);
+ args.recycle();
+ return true;
+ }
+ if (message.what != MESSAGE_SEND_MOTION_EVENT) {
+ throw new IllegalArgumentException("Unknown message: " + message.what);
+ }
+ 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);
+ }
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f21eba1..4f758106 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -255,7 +255,9 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static android.content.pm.PackageManager.GET_PROVIDERS;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.content.pm.PackageManager.MATCH_ENCRYPTION_UNAWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -515,9 +517,9 @@
private Installer mInstaller;
/** Run all ActivityStacks through this */
- ActivityStackSupervisor mStackSupervisor;
+ final ActivityStackSupervisor mStackSupervisor;
- ActivityStarter mActivityStarter;
+ final ActivityStarter mActivityStarter;
/** Task stack change listeners. */
private RemoteCallbackList<ITaskStackListener> mTaskStackListeners =
@@ -1954,8 +1956,10 @@
break;
}
case SYSTEM_USER_UNLOCK_MSG: {
- mSystemServiceManager.unlockUser(msg.arg1);
- mRecentTasks.cleanupLocked(msg.arg1);
+ final int userId = msg.arg1;
+ mSystemServiceManager.unlockUser(userId);
+ mRecentTasks.cleanupLocked(userId);
+ installEncryptionUnawareProviders(userId);
break;
}
case SYSTEM_USER_CURRENT_MSG: {
@@ -3643,11 +3647,9 @@
return false;
}
Intent intent = getHomeIntent();
- ActivityInfo aInfo =
- resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
+ ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
if (aInfo != null) {
- intent.setComponent(new ComponentName(
- aInfo.applicationInfo.packageName, aInfo.name));
+ intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
// Don't do this if the home app is currently being
// instrumented.
aInfo = new ActivityInfo(aInfo);
@@ -10826,6 +10828,41 @@
}
/**
+ * When a user is unlocked, we need to install encryption-unaware providers
+ * belonging to any running apps.
+ */
+ private void installEncryptionUnawareProviders(int userId) {
+ synchronized (this) {
+ final int NP = mProcessNames.getMap().size();
+ for (int ip = 0; ip < NP; ip++) {
+ final SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
+ final int NA = apps.size();
+ for (int ia = 0; ia < NA; ia++) {
+ final ProcessRecord app = apps.valueAt(ia);
+ if (app.userId != userId || app.thread == null) continue;
+
+ final int NG = app.pkgList.size();
+ for (int ig = 0; ig < NG; ig++) {
+ try {
+ final String pkgName = app.pkgList.keyAt(ig);
+ final PackageInfo pkgInfo = AppGlobals.getPackageManager()
+ .getPackageInfo(pkgName,
+ GET_PROVIDERS | MATCH_ENCRYPTION_UNAWARE, userId);
+ if (pkgInfo != null && !ArrayUtils.isEmpty(pkgInfo.providers)) {
+ for (ProviderInfo provInfo : pkgInfo.providers) {
+ Log.v(TAG, "Installing " + provInfo);
+ app.thread.scheduleInstallProvider(provInfo);
+ }
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
* Allows apps to retrieve the MIME type of a URI.
* If an app is in the same user as the ContentProvider, or if it is allowed to interact across
* users, then it does not need permission to access the ContentProvider.
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e123dbd..c44b4cf 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -3368,9 +3368,9 @@
try {
ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
destIntent.getComponent(), 0, srec.userId);
- int res = mService.mActivityStarter.startActivityLocked(srec.app.thread, destIntent,
- null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null, null,
- parent.appToken, null, 0, -1, parent.launchedFromUid,
+ int res = mService.mActivityStarter.startActivityLocked(srec.app.thread,
+ destIntent, null /*ephemeralIntent*/, null, aInfo, null /*rInfo*/, null,
+ null, parent.appToken, null, 0, -1, parent.launchedFromUid,
parent.launchedFromPackage, -1, parent.launchedFromUid, 0, null,
false, true, null, null, null);
foundParentInTask = res == ActivityManager.START_SUCCESS;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index f712613..cfa4433 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -32,12 +32,12 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
-import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP;
import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -45,6 +45,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
@@ -63,8 +64,8 @@
import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS;
+import static com.android.server.am.EventLogTags.AM_NEW_INTENT;
-import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppGlobals;
@@ -529,7 +530,10 @@
// switch... just dismiss the keyguard now, because we
// probably want to see whatever is behind it.
mSupervisor.notifyActivityDrawnForKeyguard();
+ } else {
+ launchRecentsAppIfNeeded(stack);
}
+
return err;
}
@@ -863,6 +867,28 @@
intentActivity.task.setIntent(mStartActivity);
}
+ // This code path leads to delivering a new intent, we want to make sure we schedule it
+ // as the first operation, in case the activity will be resumed as a result of later
+ // operations.
+ if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
+ || mLaunchSingleInstance || mLaunchSingleTask) {
+ // In this situation we want to remove all activities from the task up to the one
+ // being started. In most cases this means we are resetting the task to its initial
+ // state.
+ final ActivityRecord top = intentActivity.task.performClearTaskLocked(
+ mStartActivity, mLaunchFlags);
+ if (top != null) {
+ if (top.frontOfTask) {
+ // Activity aliases may mean we use different intents for the top activity,
+ // so make sure the task now has the identity of the new intent.
+ top.task.setIntent(mStartActivity);
+ }
+ ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.task);
+ top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
+ mStartActivity.launchedFromPackage);
+ }
+ }
+
intentActivity = setTargetStackAndMoveToFrontIfNeeded(intentActivity);
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
@@ -904,7 +930,7 @@
&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| mLaunchSingleTop || mLaunchSingleTask);
if (dontStart) {
- ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
+ ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.task);
// For paranoia, make sure we have correctly resumed the top activity.
topStack.mLastPausedActivity = null;
if (mDoResume) {
@@ -1006,6 +1032,16 @@
return START_SUCCESS;
}
+ private void launchRecentsAppIfNeeded(ActivityStack topStack) {
+ if (topStack.mStackId == HOME_STACK_ID && mTargetStack.mStackId == DOCKED_STACK_ID) {
+ // We launch an activity while being in home stack, which means either launcher or
+ // recents into docked stack. We don't want the launched activity to be alone in a
+ // docked stack, so we want to immediately launch recents too.
+ if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
+ mWindowManager.showRecentApps();
+ }
+ }
+
private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask,
boolean doResume, int startFlags, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
@@ -1305,20 +1341,9 @@
mReuseTask.setIntent(mStartActivity);
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| mLaunchSingleInstance || mLaunchSingleTask) {
- // In this situation we want to remove all activities from the task up to the one
- // being started. In most cases this means we are resetting the task to its initial
- // state.
ActivityRecord top = intentActivity.task.performClearTaskLocked(mStartActivity,
mLaunchFlags);
- if (top != null) {
- if (top.frontOfTask) {
- // Activity aliases may mean we use different intents for the top activity,
- // so make sure the task now has the identity of the new intent.
- top.task.setIntent(mStartActivity);
- }
- ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, mStartActivity, top.task);
- top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
- } else {
+ if (top == null) {
// A special case: we need to start the activity because it is not currently
// running, and the caller has asked to clear the current task to have this
// activity at the top.
@@ -1343,7 +1368,7 @@
// desires.
if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
- ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, mStartActivity,
+ ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
intentActivity.task);
if (intentActivity.frontOfTask) {
intentActivity.task.setIntent(mStartActivity);
@@ -1439,7 +1464,7 @@
ActivityRecord top = sourceTask.performClearTaskLocked(mStartActivity, mLaunchFlags);
mKeepCurTransition = true;
if (top != null) {
- ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, mStartActivity, top.task);
+ ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.task);
top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
// For paranoia, make sure we have correctly resumed the top activity.
mTargetStack.mLastPausedActivity = null;
@@ -1458,7 +1483,7 @@
final TaskRecord task = top.task;
task.moveActivityToFrontLocked(top);
top.updateOptionsLocked(mOptions);
- ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, mStartActivity, task);
+ ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, task);
top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
mTargetStack.mLastPausedActivity = null;
if (mDoResume) {
@@ -1495,7 +1520,7 @@
if (top != null && top.realActivity.equals(mStartActivity.realActivity) && top.userId == mStartActivity.userId) {
if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| mLaunchSingleTop || mLaunchSingleTask) {
- ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task);
+ ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.task);
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
// We don't need to start a new activity, and the client said not to do
// anything if that is the case, so this is it!
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 4eabe36..3530d80 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -613,9 +613,10 @@
if (periodicToReschedule.hasDeadlineConstraint()) {
runEarly = Math.max(periodicToReschedule.getLatestRunTimeElapsed() - elapsedNow, 0L);
}
- long newEarliestRunTimeElapsed = elapsedNow + runEarly;
+ long flex = periodicToReschedule.getJob().getFlexMillis();
long period = periodicToReschedule.getJob().getIntervalMillis();
- long newLatestRuntimeElapsed = newEarliestRunTimeElapsed + period;
+ long newLatestRuntimeElapsed = elapsedNow + runEarly + period;
+ long newEarliestRunTimeElapsed = newLatestRuntimeElapsed - flex;
if (DEBUG) {
Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 472e8f6..b8aa9dd 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -397,6 +397,7 @@
if (jobStatus.getJob().isPeriodic()) {
out.startTag(null, XML_TAG_PERIODIC);
out.attribute(null, "period", Long.toString(job.getIntervalMillis()));
+ out.attribute(null, "flex", Long.toString(job.getFlexMillis()));
} else {
out.startTag(null, XML_TAG_ONEOFF);
}
@@ -594,13 +595,17 @@
String val = parser.getAttributeValue(null, "period");
final long periodMillis = Long.valueOf(val);
jobBuilder.setPeriodic(periodMillis);
- // As a sanity check, cap the recreated run time to be no later than 2 periods
+ val = parser.getAttributeValue(null, "flex");
+ final long flexMillis = (val != null) ? Long.valueOf(val) : periodMillis;
+ // As a sanity check, cap the recreated run time to be no later than flex+period
// from now. This is the latest the periodic could be pushed out. This could
- // happen if the periodic ran early (at the start of its period), and then the
+ // happen if the periodic ran early (at flex time before period), and then the
// device rebooted.
- if (elapsedRuntimes.second > elapsedNow + 2 * periodMillis) {
- final long clampedEarlyRuntimeElapsed = elapsedNow + periodMillis;
- final long clampedLateRuntimeElapsed = elapsedNow + 2 * periodMillis;
+ if (elapsedRuntimes.second > elapsedNow + periodMillis + flexMillis) {
+ final long clampedLateRuntimeElapsed = elapsedNow + flexMillis
+ + periodMillis;
+ final long clampedEarlyRuntimeElapsed = clampedLateRuntimeElapsed
+ - flexMillis;
Slog.w(TAG,
String.format("Periodic job for uid='%d' persisted run-time is" +
" too big [%s, %s]. Clamping to [%s,%s]",
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index c02611f..060a93e 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -96,8 +96,8 @@
final long elapsedNow = SystemClock.elapsedRealtime();
if (job.isPeriodic()) {
- earliestRunTimeElapsedMillis = elapsedNow;
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
+ earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis();
} else {
earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e787eda..018bf2d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -28,6 +28,7 @@
import static android.service.notification.NotificationAssistantService.REASON_LISTENER_CANCEL_ALL;
import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_BANNED;
import static android.service.notification.NotificationAssistantService.REASON_PACKAGE_CHANGED;
+import static android.service.notification.NotificationAssistantService.REASON_TOPIC_BANNED;
import static android.service.notification.NotificationAssistantService.REASON_USER_STOPPED;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
@@ -741,7 +742,7 @@
for (String pkgName : pkgList) {
if (cancelNotifications) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkgName, 0, 0, !queryRestart,
- changeUserId, REASON_PACKAGE_CHANGED, null);
+ changeUserId, REASON_PACKAGE_CHANGED, null, null);
}
}
}
@@ -774,7 +775,7 @@
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0) {
cancelAllNotificationsInt(MY_UID, MY_PID, null, 0, 0, true, userHandle,
- REASON_USER_STOPPED, null);
+ REASON_USER_STOPPED, null, null);
}
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
// turn off LED when user passes through lock screen
@@ -1051,7 +1052,7 @@
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, 0, 0, true, UserHandle.getUserId(uid),
- REASON_PACKAGE_BANNED, null);
+ REASON_PACKAGE_BANNED, null, null);
}
}
@@ -1209,7 +1210,7 @@
// running foreground services.
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
- REASON_APP_CANCEL_ALL, null);
+ REASON_APP_CANCEL_ALL, null, null);
}
@Override
@@ -1266,6 +1267,11 @@
public void setTopicImportance(String pkg, int uid, Notification.Topic topic,
int importance) {
enforceSystemOrSystemUI("Caller not system or systemui");
+ if (NotificationListenerService.Ranking.IMPORTANCE_NONE == importance) {
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, 0, 0, true,
+ UserHandle.getUserId(uid),
+ REASON_TOPIC_BANNED, topic, null);
+ }
mRankingHelper.setTopicImportance(pkg, uid, topic, importance);
savePolicyFile();
}
@@ -2284,8 +2290,9 @@
mRankingHelper.extractSignals(r);
savePolicyFile();
- // blocked apps
- if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
+ // blocked apps/topics
+ if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
+ || !noteNotificationOp(pkg, callingUid)) {
if (!isSystemNotification) {
Slog.e(TAG, "Suppressing notification from package " + pkg
+ " by user request.");
@@ -3067,11 +3074,11 @@
}
/**
- * Cancels all notifications from a given package that have all of the
+ * Cancels all notifications from a given package or topic that have all of the
* {@code mustHaveFlags}.
*/
boolean cancelAllNotificationsInt(int callingUid, int callingPid, String pkg, int mustHaveFlags,
- int mustNotHaveFlags, boolean doit, int userId, int reason,
+ int mustNotHaveFlags, boolean doit, int userId, int reason, Notification.Topic topic,
ManagedServiceInfo listener) {
String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancelAll(callingUid, callingPid,
@@ -3099,6 +3106,10 @@
if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
continue;
}
+ if (topic != null
+ && !topic.getId().equals(r.getNotification().getTopic().getId())) {
+ continue;
+ }
if (canceledNotifications == null) {
canceledNotifications = new ArrayList<>();
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index f1fd42c..ce4ecd3 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -423,14 +423,16 @@
/**
* Sets the default importance for all new topics that appear in the future, and resets
- * the importance of all current topics.
+ * the importance of all current topics (unless the app is being blocked).
*/
@Override
public void setAppImportance(String pkgName, int uid, int importance) {
final Record r = getOrCreateRecord(pkgName, uid);
r.importance = importance;
- for (Topic t : r.topics.values()) {
- t.importance = importance;
+ if (Ranking.IMPORTANCE_NONE != importance) {
+ for (Topic t : r.topics.values()) {
+ t.importance = importance;
+ }
}
updateConfig();
}
@@ -483,7 +485,9 @@
pw.print(prefix);
pw.println("per-package config:");
}
+ pw.println("Records:");
dumpRecords(pw, prefix, filter, mRecords);
+ pw.println("Restored without uid:");
dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 870ae89..e3ed0c1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -318,6 +318,8 @@
static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
+ private static final boolean DISABLE_EPHEMERAL_APPS = true;
+
private static final int RADIO_UID = Process.PHONE_UID;
private static final int LOG_UID = Process.LOG_UID;
private static final int NFC_UID = Process.NFC_UID;
@@ -3119,7 +3121,7 @@
/**
* Update given flags based on encryption status of current user.
*/
- private int updateFlagsForEncryption(int flags, int userId) {
+ private int updateFlags(int flags, int userId) {
if ((flags & (PackageManager.MATCH_ENCRYPTION_UNAWARE
| PackageManager.MATCH_ENCRYPTION_AWARE)) != 0) {
// Caller expressed an explicit opinion about what encryption
@@ -3133,6 +3135,12 @@
flags |= PackageManager.MATCH_ENCRYPTION_AWARE;
}
}
+
+ // Safe mode means we should ignore any third-party apps
+ if (mSafeMode) {
+ flags |= PackageManager.MATCH_SYSTEM_ONLY;
+ }
+
return flags;
}
@@ -3160,7 +3168,7 @@
Log.w(TAG, "Caller hasn't been triaged for missing apps; they asked about " + cookie
+ " with flags 0x" + Integer.toHexString(flags), new Throwable());
}
- return updateFlagsForEncryption(flags, userId);
+ return updateFlags(flags, userId);
}
/**
@@ -3192,7 +3200,7 @@
Log.w(TAG, "Caller hasn't been triaged for missing apps; they asked about " + cookie
+ " with flags 0x" + Integer.toHexString(flags), new Throwable());
}
- return updateFlagsForEncryption(flags, userId);
+ return updateFlags(flags, userId);
}
/**
@@ -4469,6 +4477,9 @@
private boolean isEphemeralAllowed(
Intent intent, List<ResolveInfo> resolvedActivites, int userId) {
// Short circuit and return early if possible.
+ if (DISABLE_EPHEMERAL_APPS) {
+ return false;
+ }
final int callingUser = UserHandle.getCallingUserId();
if (callingUser != UserHandle.USER_SYSTEM) {
return false;
@@ -5803,6 +5814,10 @@
@Override
public ParceledListSlice<EphemeralApplicationInfo> getEphemeralApplications(int userId) {
+ if (DISABLE_EPHEMERAL_APPS) {
+ return null;
+ }
+
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS,
"getEphemeralApplications");
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
@@ -5821,6 +5836,10 @@
public boolean isEphemeralApplication(String packageName, int userId) {
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
"isEphemeral");
+ if (DISABLE_EPHEMERAL_APPS) {
+ return false;
+ }
+
if (!isCallerSameApp(packageName)) {
return false;
}
@@ -5835,6 +5854,10 @@
@Override
public byte[] getEphemeralApplicationCookie(String packageName, int userId) {
+ if (DISABLE_EPHEMERAL_APPS) {
+ return null;
+ }
+
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
"getCookie");
if (!isCallerSameApp(packageName)) {
@@ -5848,6 +5871,10 @@
@Override
public boolean setEphemeralApplicationCookie(String packageName, byte[] cookie, int userId) {
+ if (DISABLE_EPHEMERAL_APPS) {
+ return true;
+ }
+
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
"setCookie");
if (!isCallerSameApp(packageName)) {
@@ -5861,6 +5888,10 @@
@Override
public Bitmap getEphemeralApplicationIcon(String packageName, int userId) {
+ if (DISABLE_EPHEMERAL_APPS) {
+ return null;
+ }
+
mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_EPHEMERAL_APPS,
"getEphemeralApplicationIcon");
enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
@@ -5916,8 +5947,6 @@
: null;
return ps != null
&& mSettings.isEnabledAndMatchLPr(provider.info, flags, userId)
- && (!mSafeMode || (provider.info.applicationInfo.flags
- &ApplicationInfo.FLAG_SYSTEM) != 0)
? PackageParser.generateProviderInfo(provider, flags,
ps.readUserState(userId), userId)
: null;
@@ -5972,9 +6001,7 @@
&& (processName == null
|| (p.info.processName.equals(processName)
&& UserHandle.isSameApp(p.info.applicationInfo.uid, uid)))
- && mSettings.isEnabledAndMatchLPr(p.info, flags, userId)
- && (!mSafeMode
- || (p.info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) {
+ && mSettings.isEnabledAndMatchLPr(p.info, flags, userId)) {
if (finalList == null) {
finalList = new ArrayList<ProviderInfo>(3);
}
@@ -9240,10 +9267,6 @@
return null;
}
final PackageParser.Activity activity = info.activity;
- if (mSafeMode && (activity.info.applicationInfo.flags
- &ApplicationInfo.FLAG_SYSTEM) == 0) {
- return null;
- }
PackageSetting ps = (PackageSetting) activity.owner.mExtras;
if (ps == null) {
return null;
@@ -9464,10 +9487,6 @@
return null;
}
final PackageParser.Service service = info.service;
- if (mSafeMode && (service.info.applicationInfo.flags
- &ApplicationInfo.FLAG_SYSTEM) == 0) {
- return null;
- }
PackageSetting ps = (PackageSetting) service.owner.mExtras;
if (ps == null) {
return null;
@@ -9687,10 +9706,6 @@
return null;
}
final PackageParser.Provider provider = info.provider;
- if (mSafeMode && (provider.info.applicationInfo.flags
- & ApplicationInfo.FLAG_SYSTEM) == 0) {
- return null;
- }
PackageSetting ps = (PackageSetting) provider.owner.mExtras;
if (ps == null) {
return null;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3f9ce7a..1a79d3c3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3799,63 +3799,11 @@
}
boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, int flags, int userId) {
- return isEnabledLPr(componentInfo, flags, userId)
- && isMatchLPr(componentInfo, flags);
- }
+ final PackageSetting ps = mPackages.get(componentInfo.packageName);
+ if (ps == null) return false;
- private boolean isEnabledLPr(ComponentInfo componentInfo, int flags, int userId) {
- if ((flags & MATCH_DISABLED_COMPONENTS) != 0) {
- return true;
- }
- final PackageSetting packageSettings = mPackages.get(componentInfo.packageName);
- if (PackageManagerService.DEBUG_SETTINGS) {
- Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = "
- + componentInfo.packageName + " componentName = " + componentInfo.name);
- Log.v(PackageManagerService.TAG, "enabledComponents: "
- + compToString(packageSettings.getEnabledComponents(userId)));
- Log.v(PackageManagerService.TAG, "disabledComponents: "
- + compToString(packageSettings.getDisabledComponents(userId)));
- }
- if (packageSettings == null) {
- return false;
- }
- PackageUserState ustate = packageSettings.readUserState(userId);
- if ((flags & MATCH_DISABLED_UNTIL_USED_COMPONENTS) != 0) {
- if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
- return true;
- }
- }
- if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED
- || ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_USER
- || ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
- || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled
- && ustate.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) {
- return false;
- }
- if (ustate.enabledComponents != null
- && ustate.enabledComponents.contains(componentInfo.name)) {
- return true;
- }
- if (ustate.disabledComponents != null
- && ustate.disabledComponents.contains(componentInfo.name)) {
- return false;
- }
- return componentInfo.enabled;
- }
-
- private boolean isMatchLPr(ComponentInfo componentInfo, int flags) {
- if ((flags & MATCH_SYSTEM_ONLY) != 0) {
- final PackageSetting ps = mPackages.get(componentInfo.packageName);
- if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
- return false;
- }
- }
-
- final boolean matchesUnaware = ((flags & MATCH_ENCRYPTION_UNAWARE) != 0)
- && !componentInfo.encryptionAware;
- final boolean matchesAware = ((flags & MATCH_ENCRYPTION_AWARE) != 0)
- && componentInfo.encryptionAware;
- return matchesUnaware || matchesAware;
+ final PackageUserState userState = ps.readUserState(userId);
+ return userState.isMatch(componentInfo, flags);
}
String getInstallerPackageNameLPr(String packageName) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f13d964..de1c1ea 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3788,7 +3788,7 @@
// size. We need to do this directly, instead of relying on
// it to bubble up from the nav bar, because this needs to
// change atomically with screen rotations.
- mNavigationBarOnBottom = (!mNavigationBarCanMove || displayWidth < displayHeight);
+ mNavigationBarOnBottom = isNavigationBarOnBottom(displayWidth, displayHeight);
if (mNavigationBarOnBottom) {
// It's a system nav bar or a portrait screen; nav bar goes on bottom.
int top = displayHeight - overscanBottom
@@ -3859,6 +3859,10 @@
return false;
}
+ private boolean isNavigationBarOnBottom(int displayWidth, int displayHeight) {
+ return !mNavigationBarCanMove || displayWidth < displayHeight;
+ }
+
/** {@inheritDoc} */
@Override
public int getSystemDecorLayerLw() {
@@ -5931,6 +5935,22 @@
}
}
+ @Override
+ public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+ Rect outInsets) {
+ outInsets.setEmpty();
+ if (mStatusBar != null) {
+ outInsets.top = mStatusBarHeight;
+ }
+ if (mNavigationBar != null) {
+ if (isNavigationBarOnBottom(displayWidth, displayHeight)) {
+ outInsets.bottom = getNavigationBarHeight(displayRotation, mUiMode);
+ } else {
+ outInsets.right = getNavigationBarWidth(displayRotation, mUiMode);
+ }
+ }
+ }
+
void sendCloseSystemWindows() {
PhoneWindow.sendCloseSystemWindows(mContext, null);
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 7be0ead..c3a6f5d 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -291,15 +291,24 @@
// If the user has chosen provider, use that
for (WebViewProviderInfo provider : providers) {
- if (provider.packageName.equals(userChosenProvider)) {
+ if (provider.packageName.equals(userChosenProvider) && provider.isEnabled()) {
return provider.getPackageInfo();
}
}
- // User did not choose, or the choice failed, use the most stable provider available
+ // User did not choose, or the choice failed; use the most stable provider that is
+ // enabled and available by default (not through user choice).
+ for (WebViewProviderInfo provider : providers) {
+ if (provider.isAvailableByDefault() && provider.isEnabled()) {
+ return provider.getPackageInfo();
+ }
+ }
+
+ // Could not find any enabled package either, use the most stable provider.
for (WebViewProviderInfo provider : providers) {
return provider.getPackageInfo();
}
+
mAnyWebViewInstalled = false;
throw new WebViewFactory.MissingWebViewPackageException(
"Could not find a loadable WebView package");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 51787b0..a9025bd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -393,7 +393,7 @@
}
mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
}
- if (task.isDockedInEffect() && !task.isResizeable()) {
+ if (task.isTwoFingerScrollMode()) {
stack.getBounds(mTmpRect);
mNonResizeableRegion.op(mTmpRect, Region.Op.UNION);
break;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b4ddebc..6bb3e20 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -558,8 +558,9 @@
}
boolean isResizeableByDockedStack() {
- return mStack != null && getDisplayContent().getDockedStackLocked() != null &&
- StackId.isTaskResizeableByDockedStack(mStack.mStackId);
+ final DisplayContent displayContent = getDisplayContent();
+ return displayContent != null && displayContent.getDockedStackLocked() != null
+ && mStack != null && StackId.isTaskResizeableByDockedStack(mStack.mStackId);
}
/**
@@ -570,6 +571,10 @@
return inDockedWorkspace() || isResizeableByDockedStack();
}
+ boolean isTwoFingerScrollMode() {
+ return isDockedInEffect() && !isResizeable();
+ }
+
WindowState getTopVisibleAppMainWindow() {
final AppWindowToken token = getTopVisibleAppToken();
return token != null ? token.findMainWindow() : null;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index fc6ad70..e75780f 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -18,6 +18,7 @@
import android.app.ActivityManager.StackId;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Debug;
import android.util.EventLog;
@@ -26,6 +27,9 @@
import android.view.DisplayInfo;
import android.view.Surface;
+import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
+import com.android.internal.policy.DockedDividerUtils;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
@@ -126,7 +130,7 @@
Configuration config = configs.get(task.mTaskId);
if (config != null) {
Rect bounds = taskBounds.get(task.mTaskId);
- if (!task.isResizeable() && task.isDockedInEffect()) {
+ if (task.isTwoFingerScrollMode()) {
// This is a non-resizeable task that's docked (or side-by-side to the docked
// stack). It might have been scrolled previously, and after the stack resizing,
// it might no longer fully cover the stack area.
@@ -245,19 +249,66 @@
setBounds(null);
} else {
mTmpRect2.set(mBounds);
- mDisplayContent.rotateBounds(
- mRotation, mDisplayContent.getDisplayInfo().rotation, mTmpRect2);
- if (setBounds(mTmpRect2)) {
- // Post message to inform activity manager of the bounds change simulating
- // a one-way call. We do this to prevent a deadlock between window manager
- // lock and activity manager lock been held.
- mService.mH.sendMessage(mService.mH.obtainMessage(
- RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mBounds));
+ final int newRotation = mDisplayContent.getDisplayInfo().rotation;
+ if (mRotation == newRotation) {
+ setBounds(mTmpRect2);
}
+
+ // If the rotation changes, we'll handle it in updateBoundsAfterRotation
}
}
}
+ /**
+ * Updates the bounds after rotating the screen. We can't handle it in
+ * {@link #updateDisplayInfo} because at that point the configuration might not be fully updated
+ * yet.
+ */
+ void updateBoundsAfterRotation() {
+ final int newRotation = getDisplayInfo().rotation;
+ mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
+ if (mStackId == DOCKED_STACK_ID) {
+ snapDockedStackAfterRotation(mTmpRect2);
+ }
+
+ // Post message to inform activity manager of the bounds change simulating
+ // a one-way call. We do this to prevent a deadlock between window manager
+ // lock and activity manager lock been held.
+ mService.mH.sendMessage(mService.mH.obtainMessage(
+ RESIZE_STACK, mStackId, 0 /*allowResizeInDockedMode*/, mTmpRect2));
+ }
+
+ /**
+ * Snaps the bounds after rotation to the closest snap target for the docked stack.
+ */
+ private void snapDockedStackAfterRotation(Rect outBounds) {
+
+ // Calculate the current position.
+ final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+ final int dividerSize = mService.getDefaultDisplayContentLocked()
+ .getDockedDividerController().getContentWidth();
+ final int dockSide = getDockSide(outBounds);
+ final int dividerPosition = DockedDividerUtils.calculatePositionForBounds(outBounds,
+ dockSide, dividerSize);
+ final int displayWidth = mDisplayContent.getDisplayInfo().logicalWidth;
+ final int displayHeight = mDisplayContent.getDisplayInfo().logicalHeight;
+
+ // Snap the position to a target.
+ final int rotation = displayInfo.rotation;
+ final int orientation = mService.mCurConfiguration.orientation;
+ mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight, outBounds);
+ final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
+ mService.mContext.getResources(),
+ 0 /* minFlingVelocityPxPerSecond */, displayWidth, displayHeight,
+ dividerSize, orientation == Configuration.ORIENTATION_PORTRAIT, outBounds);
+ final SnapTarget target = algorithm.calculateNonDismissingSnapTarget(dividerPosition);
+
+ // Recalculate the bounds based on the position of the target.
+ DockedDividerUtils.calculateBoundsForPosition(target.position, dockSide,
+ outBounds, displayInfo.logicalWidth, displayInfo.logicalHeight,
+ dividerSize);
+ }
+
boolean isAnimating() {
for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens;
@@ -682,6 +733,10 @@
* information which side of the screen was the dock anchored.
*/
int getDockSide() {
+ return getDockSide(mBounds);
+ }
+
+ int getDockSide(Rect bounds) {
if (mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId)) {
return DOCKED_INVALID;
}
@@ -692,14 +747,14 @@
final int orientation = mService.mCurConfiguration.orientation;
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
// Portrait mode, docked either at the top or the bottom.
- if (mBounds.top - mTmpRect.top < mTmpRect.bottom - mBounds.bottom) {
+ if (bounds.top - mTmpRect.top < mTmpRect.bottom - bounds.bottom) {
return DOCKED_TOP;
} else {
return DOCKED_BOTTOM;
}
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
// Landscape mode, docked either on the left or on the right.
- if (mBounds.left - mTmpRect.left < mTmpRect.right - mBounds.right) {
+ if (bounds.left - mTmpRect.left < mTmpRect.right - bounds.right) {
return DOCKED_LEFT;
} else {
return DOCKED_RIGHT;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f858abe..685df25 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1990,6 +1990,10 @@
}
}
+ // If the window is being added to a task that's docked but non-resizeable,
+ // we need to update this new window's scroll position when it's added.
+ win.applyScrollIfNeeded();
+
if (type == TYPE_DOCK_DIVIDER) {
getDefaultDisplayContentLocked().getDockedDividerController().setWindow(win);
}
@@ -2977,7 +2981,7 @@
+ " frame=" + frame + " insets=" + insets + " surfaceInsets=" + surfaceInsets);
Animation a = mAppTransition.loadAnimation(lp, transit, enter,
mCurConfiguration.orientation, frame, insets, surfaceInsets, isVoiceInteraction,
- freeform, atoken.mTask.mTaskId);
+ !fullscreen, atoken.mTask.mTaskId);
if (a != null) {
if (DEBUG_ANIM) {
RuntimeException e = null;
@@ -3523,6 +3527,7 @@
}
}
+ @Override
public void setNewConfiguration(Configuration config) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
"setNewConfiguration()")) {
@@ -3536,10 +3541,20 @@
mWaitingForConfig = false;
mLastFinishedFreezeSource = "new-config";
}
+ if (orientationChanged) {
+ updateTaskStackBoundsAfterRotation();
+ }
mWindowPlacerLocked.performSurfacePlacement();
}
}
+ private void updateTaskStackBoundsAfterRotation() {
+ for (int stackNdx = mStackIdToStack.size() - 1; stackNdx >= 0; stackNdx--) {
+ final TaskStack stack = mStackIdToStack.valueAt(stackNdx);
+ stack.updateBoundsAfterRotation();
+ }
+ }
+
@Override
public void setAppOrientation(IApplicationToken token, int requestedOrientation) {
if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b7fd60f..058fa67 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1458,15 +1458,24 @@
}
boolean inDockedWorkspace() {
- Task task = getTask();
+ final Task task = getTask();
return task != null && task.inDockedWorkspace();
}
boolean isDockedInEffect() {
- Task task = getTask();
+ final Task task = getTask();
return task != null && task.isDockedInEffect();
}
+ void applyScrollIfNeeded() {
+ final Task task = getTask();
+ if (task != null && task.isTwoFingerScrollMode()) {
+ task.getDimBounds(mTmpRect);
+ mXOffset = mTmpRect.left;
+ mYOffset = mTmpRect.top;
+ }
+ }
+
int getTouchableRegion(Region region, int flags) {
final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
if (modal && mAppToken != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dd58b3c..380763a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -388,6 +388,7 @@
private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
private static final String TAG_DISABLE_CAMERA = "disable-camera";
private static final String TAG_DISABLE_CALLER_ID = "disable-caller-id";
+ private static final String TAG_DISABLE_CONTACTS_SEARCH = "disable-contacts-search";
private static final String TAG_DISABLE_BLUETOOTH_CONTACT_SHARING
= "disable-bt-contacts-sharing";
private static final String TAG_DISABLE_SCREEN_CAPTURE = "disable-screen-capture";
@@ -476,6 +477,7 @@
boolean encryptionRequested = false;
boolean disableCamera = false;
boolean disableCallerId = false;
+ boolean disableContactsSearch = false;
boolean disableBluetoothContactSharing = true;
boolean disableScreenCapture = false; // Can only be set by a device/profile owner.
boolean requireAutoTime = false; // Can only be set by a device owner.
@@ -638,6 +640,11 @@
out.attribute(null, ATTR_VALUE, Boolean.toString(disableCallerId));
out.endTag(null, TAG_DISABLE_CALLER_ID);
}
+ if (disableContactsSearch) {
+ out.startTag(null, TAG_DISABLE_CONTACTS_SEARCH);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(disableContactsSearch));
+ out.endTag(null, TAG_DISABLE_CONTACTS_SEARCH);
+ }
if (disableBluetoothContactSharing) {
out.startTag(null, TAG_DISABLE_BLUETOOTH_CONTACT_SHARING);
out.attribute(null, ATTR_VALUE,
@@ -809,6 +816,9 @@
} else if (TAG_DISABLE_CALLER_ID.equals(tag)) {
disableCallerId = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_DISABLE_CONTACTS_SEARCH.equals(tag)) {
+ disableContactsSearch = Boolean.parseBoolean(
+ parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_DISABLE_BLUETOOTH_CONTACT_SHARING.equals(tag)) {
disableBluetoothContactSharing = Boolean.parseBoolean(parser
.getAttributeValue(null, ATTR_VALUE));
@@ -1034,6 +1044,8 @@
pw.println(disableCamera);
pw.print(prefix); pw.print("disableCallerId=");
pw.println(disableCallerId);
+ pw.print(prefix); pw.print("disableContactsSearch=");
+ pw.println(disableContactsSearch);
pw.print(prefix); pw.print("disableBluetoothContactSharing=");
pw.println(disableBluetoothContactSharing);
pw.print(prefix); pw.print("disableScreenCapture=");
@@ -1808,7 +1820,7 @@
if (!mHasFeature) {
return null;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
Intent resolveIntent = new Intent();
resolveIntent.setComponent(adminName);
List<ResolveInfo> infos = mContext.getPackageManager().queryBroadcastReceiversAsUser(
@@ -2411,7 +2423,7 @@
Bundle onEnableData) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_DEVICE_ADMINS, null);
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
DevicePolicyData policy = getUserData(userHandle);
DeviceAdminInfo info = findAdmin(adminReceiver, userHandle,
@@ -2457,7 +2469,7 @@
if (!mHasFeature) {
return false;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
return getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null;
}
@@ -2468,7 +2480,7 @@
if (!mHasFeature) {
return false;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
DevicePolicyData policyData = getUserData(userHandle);
return policyData.mRemovingAdmins.contains(adminReceiver);
@@ -2480,7 +2492,7 @@
if (!mHasFeature) {
return false;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (administrator == null) {
@@ -2497,7 +2509,7 @@
return Collections.EMPTY_LIST;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
DevicePolicyData policy = getUserData(userHandle);
final int N = policy.mAdminList.size();
@@ -2517,7 +2529,7 @@
if (!mHasFeature) {
return false;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
DevicePolicyData policy = getUserData(userHandle);
final int N = policy.mAdminList.size();
@@ -2535,7 +2547,7 @@
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (admin == null) {
@@ -2594,7 +2606,7 @@
if (!mHasFeature) {
return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int mode = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -2664,7 +2676,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int length = 0;
@@ -2711,7 +2723,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int length = 0;
@@ -2771,7 +2783,7 @@
if (!mHasFeature) {
return 0L;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
long timeout = 0L;
@@ -2898,7 +2910,7 @@
if (!mHasFeature) {
return 0L;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
return getPasswordExpirationLocked(who, userHandle);
}
@@ -2926,7 +2938,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int length = 0;
@@ -2970,7 +2982,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int length = 0;
@@ -3017,7 +3029,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int length = 0;
@@ -3067,7 +3079,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int length = 0;
@@ -3117,7 +3129,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int length = 0;
@@ -3167,7 +3179,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int length = 0;
@@ -3200,7 +3212,7 @@
if (!mHasFeature) {
return true;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
int id = getCredentialOwner(userHandle);
@@ -3272,7 +3284,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
ActiveAdmin admin = (who != null) ? getActiveAdminUncheckedLocked(who, userHandle)
: getAdminWithMinimumFailedPasswordsForWipeLocked(userHandle);
@@ -3285,7 +3297,7 @@
if (!mHasFeature) {
return UserHandle.USER_NULL;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
ActiveAdmin admin = getAdminWithMinimumFailedPasswordsForWipeLocked(userHandle);
return admin != null ? admin.getUserHandle().getIdentifier() : UserHandle.USER_NULL;
@@ -3584,7 +3596,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
long time = 0;
@@ -3906,7 +3918,7 @@
return;
}
final int userHandle = mInjector.userHandleGetCallingUserId();
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
// This API can only be called by an active device admin,
// so try to retrieve it to check that the caller is one.
@@ -3985,7 +3997,7 @@
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
@@ -4014,7 +4026,7 @@
if (!mHasFeature) {
return;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
// Managed Profile password can only be changed when per user encryption is present.
if (!LockPatternUtils.isSeparateWorkChallengeEnabled()) {
enforceNotManagedProfile(userHandle, "set the active password");
@@ -4083,7 +4095,7 @@
@Override
public void reportFailedPasswordAttempt(int userHandle) {
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
enforceNotManagedProfile(userHandle, "report failed password attempt");
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
@@ -4125,7 +4137,7 @@
@Override
public void reportSuccessfulPasswordAttempt(int userHandle) {
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
@@ -4209,7 +4221,7 @@
if (!mHasFeature) {
return null;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized(this) {
DevicePolicyData policy = getUserData(UserHandle.USER_SYSTEM);
// Scan through active admins and find if anyone has already
@@ -4346,7 +4358,7 @@
if (!mHasFeature) {
return false;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
// Check for permissions if a particular caller is specified
if (who != null) {
@@ -4376,7 +4388,7 @@
if (!mHasFeature) {
// Ok to return current status.
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
return getEncryptionStatus();
}
@@ -4624,7 +4636,7 @@
if (!mHasFeature) {
return 0;
}
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
long ident = mInjector.binderClearCallingIdentity();
try {
synchronized (this) {
@@ -5185,17 +5197,28 @@
}
}
- private void enforceCrossUserPermission(int userHandle) {
+ private void enforceFullCrossUsersPermission(int userHandle) {
+ enforceSystemUserOrPermission(userHandle,
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+
+ private void enforceCrossUsersPermission(int userHandle) {
+ enforceSystemUserOrPermission(userHandle,
+ android.Manifest.permission.INTERACT_ACROSS_USERS);
+ }
+
+ private void enforceSystemUserOrPermission(int userHandle, String permission) {
if (userHandle < 0) {
throw new IllegalArgumentException("Invalid userId " + userHandle);
}
final int callingUid = mInjector.binderGetCallingUid();
- if (userHandle == UserHandle.getUserId(callingUid)) return;
+ if (userHandle == UserHandle.getUserId(callingUid)) {
+ return;
+ }
if (!(UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
|| callingUid == Process.ROOT_UID)) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, "Must be system or have"
- + " INTERACT_ACROSS_USERS_FULL permission");
+ mContext.enforceCallingOrSelfPermission(permission,
+ "Must be system or have " + permission + " permission");
}
}
@@ -5405,7 +5428,7 @@
return null;
}
Preconditions.checkNotNull(agent, "agent null");
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
final String componentName = agent.flattenToString();
@@ -6068,7 +6091,7 @@
@Override
public Bundle getUserRestrictions(ComponentName who, int userHandle) {
Preconditions.checkNotNull(who, "ComponentName is null");
- enforceCrossUserPermission(userHandle);
+ enforceFullCrossUsersPermission(userHandle);
synchronized (this) {
ActiveAdmin activeAdmin = getActiveAdminUncheckedLocked(who, userHandle);
if (activeAdmin == null) {
@@ -6260,7 +6283,7 @@
@Override
public String[] getAccountTypesWithManagementDisabledAsUser(int userId) {
- enforceCrossUserPermission(userId);
+ enforceFullCrossUsersPermission(userId);
if (!mHasFeature) {
return null;
}
@@ -6332,7 +6355,7 @@
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
if (admin.disableCallerId != disabled) {
admin.disableCallerId = disabled;
- saveSettingsLocked(UserHandle.getCallingUserId());
+ saveSettingsLocked(mInjector.userHandleGetCallingUserId());
}
}
}
@@ -6352,8 +6375,7 @@
@Override
public boolean getCrossProfileCallerIdDisabledForUser(int userId) {
- // TODO: Should there be a check to make sure this relationship is within a profile group?
- //enforceSystemProcess("getCrossProfileCallerIdDisabled can only be called by system");
+ enforceCrossUsersPermission(userId);
synchronized (this) {
ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
return (admin != null) ? admin.disableCallerId : false;
@@ -6361,6 +6383,44 @@
}
@Override
+ public void setCrossProfileContactsSearchDisabled(ComponentName who, boolean disabled) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ if (admin.disableContactsSearch != disabled) {
+ admin.disableContactsSearch = disabled;
+ saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+ }
+ }
+ }
+
+ @Override
+ public boolean getCrossProfileContactsSearchDisabled(ComponentName who) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ return admin.disableContactsSearch;
+ }
+ }
+
+ @Override
+ public boolean getCrossProfileContactsSearchDisabledForUser(int userId) {
+ enforceCrossUsersPermission(userId);
+ synchronized (this) {
+ ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
+ return (admin != null) ? admin.disableContactsSearch : false;
+ }
+ }
+
+ @Override
public void startManagedQuickContact(String actualLookupKey, long actualContactId,
long actualDirectoryId, Intent originalIntent) {
final Intent intent = QuickContact.rebuildManagedQuickContactsIntent(
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index b18af33..b56ce73 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -687,7 +687,7 @@
.append("] PhoneAccount: ")
.append(mAccountHandle)
.append(" Capabilities: ")
- .append(mCapabilities)
+ .append(capabilitiesToString(mCapabilities))
.append(" Schemes: ");
for (String scheme : mSupportedUriSchemes) {
sb.append(scheme)
@@ -698,4 +698,42 @@
sb.append("]");
return sb.toString();
}
+
+ /**
+ * Generates a string representation of a capabilities bitmask.
+ *
+ * @param capabilities The capabilities bitmask.
+ * @return String representation of the capabilities bitmask.
+ */
+ private String capabilitiesToString(int capabilities) {
+ StringBuilder sb = new StringBuilder();
+ if (hasCapabilities(CAPABILITY_VIDEO_CALLING)) {
+ sb.append("Video ");
+ }
+ if (hasCapabilities(CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)) {
+ sb.append("Presence ");
+ }
+ if (hasCapabilities(CAPABILITY_CALL_PROVIDER)) {
+ sb.append("CallProvider ");
+ }
+ if (hasCapabilities(CAPABILITY_CALL_SUBJECT)) {
+ sb.append("CallSubject ");
+ }
+ if (hasCapabilities(CAPABILITY_CONNECTION_MANAGER)) {
+ sb.append("ConnectionMgr ");
+ }
+ if (hasCapabilities(CAPABILITY_EMERGENCY_CALLS_ONLY)) {
+ sb.append("EmergOnly ");
+ }
+ if (hasCapabilities(CAPABILITY_MULTI_USER)) {
+ sb.append("MultiUser ");
+ }
+ if (hasCapabilities(CAPABILITY_PLACE_EMERGENCY_CALLS)) {
+ sb.append("PlaceEmerg ");
+ }
+ if (hasCapabilities(CAPABILITY_SIM_SUBSCRIPTION)) {
+ sb.append("SimSub ");
+ }
+ return sb.toString();
+ }
}
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index dabf706..216603c 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -16,13 +16,23 @@
package android.telecom;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Represents attributes of video calls.
*/
public class VideoProfile implements Parcelable {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({QUALITY_UNKNOWN, QUALITY_HIGH, QUALITY_MEDIUM, QUALITY_LOW, QUALITY_DEFAULT})
+ public @interface VideoQuality {}
+
/**
* "Unknown" video quality.
* @hide
@@ -48,6 +58,14 @@
*/
public static final int QUALITY_DEFAULT = 4;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ flag = true,
+ value = {STATE_AUDIO_ONLY, STATE_TX_ENABLED, STATE_RX_ENABLED, STATE_BIDIRECTIONAL,
+ STATE_PAUSED})
+ public @interface VideoState {}
+
/**
* Used when answering or dialing a call to indicate that the call does not have a video
* component.
@@ -107,7 +125,7 @@
*
* @param videoState The video state.
*/
- public VideoProfile(int videoState) {
+ public VideoProfile(@VideoState int videoState) {
this(videoState, QUALITY_DEFAULT);
}
@@ -117,7 +135,7 @@
* @param videoState The video state.
* @param quality The video quality.
*/
- public VideoProfile(int videoState, int quality) {
+ public VideoProfile(@VideoState int videoState, @VideoQuality int quality) {
mVideoState = videoState;
mQuality = quality;
}
@@ -130,6 +148,7 @@
* {@link VideoProfile#STATE_RX_ENABLED},
* {@link VideoProfile#STATE_PAUSED}.
*/
+ @VideoState
public int getVideoState() {
return mVideoState;
}
@@ -139,6 +158,7 @@
* Valid values: {@link VideoProfile#QUALITY_HIGH}, {@link VideoProfile#QUALITY_MEDIUM},
* {@link VideoProfile#QUALITY_LOW}, {@link VideoProfile#QUALITY_DEFAULT}.
*/
+ @VideoQuality
public int getQuality() {
return mQuality;
}
@@ -211,7 +231,7 @@
* @param videoState The video state.
* @return String representation of the video state.
*/
- public static String videoStateToString(int videoState) {
+ public static String videoStateToString(@VideoState int videoState) {
StringBuilder sb = new StringBuilder();
sb.append("Audio");
@@ -240,7 +260,7 @@
* @param videoState The video state.
* @return {@code True} if the video state is audio only, {@code false} otherwise.
*/
- public static boolean isAudioOnly(int videoState) {
+ public static boolean isAudioOnly(@VideoState int videoState) {
return !hasState(videoState, VideoProfile.STATE_TX_ENABLED)
&& !hasState(videoState, VideoProfile.STATE_RX_ENABLED);
}
@@ -251,7 +271,7 @@
* @param videoState The video state.
* @return {@code True} if video transmission or reception is enabled, {@code false} otherwise.
*/
- public static boolean isVideo(int videoState) {
+ public static boolean isVideo(@VideoState int videoState) {
return hasState(videoState, VideoProfile.STATE_TX_ENABLED)
|| hasState(videoState, VideoProfile.STATE_RX_ENABLED)
|| hasState(videoState, VideoProfile.STATE_BIDIRECTIONAL);
@@ -263,7 +283,7 @@
* @param videoState The video state.
* @return {@code True} if video transmission is enabled, {@code false} otherwise.
*/
- public static boolean isTransmissionEnabled(int videoState) {
+ public static boolean isTransmissionEnabled(@VideoState int videoState) {
return hasState(videoState, VideoProfile.STATE_TX_ENABLED);
}
@@ -273,7 +293,7 @@
* @param videoState The video state.
* @return {@code True} if video reception is enabled, {@code false} otherwise.
*/
- public static boolean isReceptionEnabled(int videoState) {
+ public static boolean isReceptionEnabled(@VideoState int videoState) {
return hasState(videoState, VideoProfile.STATE_RX_ENABLED);
}
@@ -283,7 +303,7 @@
* @param videoState The video state.
* @return {@code True} if the video is bi-directional, {@code false} otherwise.
*/
- public static boolean isBidirectional(int videoState) {
+ public static boolean isBidirectional(@VideoState int videoState) {
return hasState(videoState, VideoProfile.STATE_BIDIRECTIONAL);
}
@@ -293,7 +313,7 @@
* @param videoState The video state.
* @return {@code True} if the video is paused, {@code false} otherwise.
*/
- public static boolean isPaused(int videoState) {
+ public static boolean isPaused(@VideoState int videoState) {
return hasState(videoState, VideoProfile.STATE_PAUSED);
}
@@ -304,7 +324,7 @@
* @param state The state to check.
* @return {@code True} if the state is set.
*/
- private static boolean hasState(int videoState, int state) {
+ private static boolean hasState(@VideoState int videoState, @VideoState int state) {
return (videoState & state) == state;
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6ffc026..1a040bb 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -553,6 +553,23 @@
public static final String BOOL_ALLOW_VIDEO_PAUSE =
"bool_allow_video_pause";
+
+ /**
+ * Flag indicating whether the carrier supports RCS presence indication for video calls. When
+ * {@code true}, the carrier supports RCS presence indication for video calls. When presence
+ * is supported, the device should use the
+ * {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE} bit mask and set the
+ * {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE_VT_CAPABLE} bit to indicate
+ * whether each contact supports video calling. The UI is made aware that presence is enabled
+ * via {@link android.telecom.PhoneAccount#CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE}
+ * and can choose to hide or show the video calling icon based on whether a contact supports
+ * video.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -662,6 +679,7 @@
sDefaults.putString(KEY_MMS_UA_PROF_URL_STRING, "");
sDefaults.putString(KEY_MMS_USER_AGENT_STRING, "");
sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true);
+ sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
}
/**
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 01583f56..81aa6c6 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -322,21 +322,25 @@
throw new UnsupportedOperationException();
}
+ /** @hide */
@Override
public byte[] getEphemeralCookie() {
return new byte[0];
}
+ /** @hide */
@Override
public boolean isEphemeralApplication() {
return false;
}
+ /** @hide */
@Override
public int getEphemeralCookieMaxSizeBytes() {
return 0;
}
+ /** @hide */
@Override
public boolean setEphemeralCookie(@NonNull byte[] cookie) {
return false;
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index fecfdf9..3a30230 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -175,7 +175,7 @@
.setTopic(new Notification.Topic("hello", "Hello"))
.build();
- mNM.notify(999, n);
+ mNM.notify(70, n);
}
},
@@ -194,7 +194,7 @@
.setStyle(picture)
.build();
- mNM.notify(9999, n);
+ mNM.notify(71, n);
}
},
new Test("with topic Bananas") {
@@ -211,10 +211,28 @@
.setTopic(new Notification.Topic("bananas", "Bananas"))
.build();
- mNM.notify(999, n);
+ mNM.notify(72, n);
}
},
+ new Test("with delete intent") {
+ public void run() {
+ Notification.BigTextStyle bigText = new Notification.BigTextStyle();
+ bigText.bigText("bananas are great\nso tasty\nyum\nyum\nyum\n");
+ Notification n = new Notification.Builder(NotificationTestList.this)
+ .setSmallIcon(R.drawable.icon1)
+ .setStyle(bigText)
+ .setWhen(mActivityCreateTime)
+ .setContentTitle("bananananana")
+ .setContentText("This is a banana!!!")
+ .setTopic(new Notification.Topic("bananas", "Bananas"))
+ .setDeleteIntent(makeIntent2())
+ .build();
+
+ mNM.notify(73, n);
+ }
+ },
+
new Test("Whens") {
public void run()
{
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index e1f9642..5e7d3ec 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -582,10 +582,15 @@
if (formatted && translateable) {
if (!util::verifyJavaStringFormat(*stringValue->value)) {
- mDiag->error(DiagMessage(outResource->source)
- << "multiple substitutions specified in non-positional format; "
- "did you mean to add the formatted=\"false\" attribute?");
- return false;
+ DiagMessage msg(outResource->source);
+ msg << "multiple substitutions specified in non-positional format; "
+ "did you mean to add the formatted=\"false\" attribute?";
+ if (mOptions.errorOnPositionalArguments) {
+ mDiag->error(msg);
+ return false;
+ }
+
+ mDiag->warn(msg);
}
}
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 9ad749e..51cbbe1 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -44,6 +44,11 @@
* Whether the default setting for this parser is to allow translation.
*/
bool translatable = true;
+
+ /**
+ * Whether positional arguments in formatted strings are treated as errors or warnings.
+ */
+ bool errorOnPositionalArguments = true;
};
/*
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index b3b0f65..c78670f 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -107,6 +107,7 @@
Maybe<std::string> resDir;
std::vector<std::u16string> products;
bool pseudolocalize = false;
+ bool legacyMode = false;
bool verbose = false;
};
@@ -192,6 +193,7 @@
ResourceParserOptions parserOptions;
parserOptions.products = options.products;
+ parserOptions.errorOnPositionalArguments = !options.legacyMode;
// If the filename includes donottranslate, then the default translatable is false.
parserOptions.translatable = pathData.name.find(u"donottranslate") == std::string::npos;
@@ -438,6 +440,8 @@
.optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
.optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
"(en-XA and ar-XB)", &options.pseudolocalize)
+ .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
+ &options.legacyMode)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 compile", args, &std::cerr)) {
return 1;
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 652e52f..8a87d96 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -228,29 +228,53 @@
return {};
}
+ /**
+ * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
+ * Postcondition: ResourceTable has only one package left. All others are stripped, or there
+ * is an error and false is returned.
+ */
bool verifyNoExternalPackages() {
+ auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
+ return mContext.getCompilationPackage() != pkg->name ||
+ !pkg->id ||
+ pkg->id.value() != mContext.getPackageId();
+ };
+
bool error = false;
for (const auto& package : mFinalTable.packages) {
- if (mContext.getCompilationPackage() != package->name ||
- !package->id || package->id.value() != mContext.getPackageId()) {
+ if (isExtPackageFunc(package)) {
// We have a package that is not related to the one we're building!
for (const auto& type : package->types) {
for (const auto& entry : type->entries) {
+ ResourceNameRef resName(package->name, type->type, entry->name);
+
for (const auto& configValue : entry->values) {
- mContext.getDiagnostics()->error(
- DiagMessage(configValue.value->getSource())
- << "defined resource '"
- << ResourceNameRef(package->name,
- type->type,
- entry->name)
- << "' for external package '"
- << package->name << "'");
- error = true;
+ // Special case the occurrence of an ID that is being generated for the
+ // 'android' package. This is due to legacy reasons.
+ if (valueCast<Id>(configValue.value.get()) &&
+ package->name == u"android") {
+ mContext.getDiagnostics()->warn(
+ DiagMessage(configValue.value->getSource())
+ << "generated id '" << resName
+ << "' for external package '" << package->name
+ << "'");
+ } else {
+ mContext.getDiagnostics()->error(
+ DiagMessage(configValue.value->getSource())
+ << "defined resource '" << resName
+ << "' for external package '" << package->name
+ << "'");
+ error = true;
+ }
}
}
}
}
}
+
+ auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
+ isExtPackageFunc);
+ mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
return !error;
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 31dd3d9..db4c6dc6 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -327,12 +327,19 @@
return null;
}
- // let the framework inflate the ColorStateList from the XML file.
- File f = new File(value);
- if (f.isFile()) {
- try {
- XmlPullParser parser = ParserFactory.create(f);
+ try {
+ // Get the state list file content from callback to parse PSI file
+ XmlPullParser parser = mContext.getLayoutlibCallback().getXmlFileParser(value);
+ if (parser == null) {
+ // If used with a version of Android Studio that does not implement getXmlFileParser
+ // fall back to reading the file from disk
+ File f = new File(value);
+ if (f.isFile()) {
+ parser = ParserFactory.create(f);
+ }
+ }
+ if (parser != null) {
BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
parser, mContext, resValue.isFramework());
try {
@@ -341,18 +348,18 @@
} finally {
blockParser.ensurePopped();
}
- } catch (XmlPullParserException e) {
- Bridge.getLog().error(LayoutLog.TAG_BROKEN,
- "Failed to configure parser for " + value, e, null);
- return null;
- } catch (Exception e) {
- // this is an error and not warning since the file existence is checked before
- // attempting to parse it.
- Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
- "Failed to parse file " + value, e, null);
-
- return null;
}
+ } catch (XmlPullParserException e) {
+ Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+ "Failed to configure parser for " + value, e, null);
+ return null;
+ } catch (Exception e) {
+ // this is an error and not warning since the file existence is checked before
+ // attempting to parse it.
+ Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
+ "Failed to parse file " + value, e, null);
+
+ return null;
}
try {
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
index 60514b6..8d5863b 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java
@@ -122,4 +122,35 @@
/*package*/ static boolean nativeIsSeekable(FileDescriptor fd) {
return true;
}
+
+ /**
+ * Set the newly decoded bitmap's density based on the Options.
+ *
+ * Copied from {@link BitmapFactory#setDensityFromOptions(Bitmap, Options)}.
+ */
+ @LayoutlibDelegate
+ /*package*/ static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
+ if (outputBitmap == null || opts == null) return;
+
+ final int density = opts.inDensity;
+ if (density != 0) {
+ outputBitmap.setDensity(density);
+ final int targetDensity = opts.inTargetDensity;
+ if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
+ return;
+ }
+
+ // --- Change from original implementation begins ---
+ // LayoutLib doesn't scale the nine patch when decoding it. Hence, don't change the
+ // density of the source bitmap in case of ninepatch.
+
+ if (opts.inScaled) {
+ // --- Change from original implementation ends. ---
+ outputBitmap.setDensity(targetDensity);
+ }
+ } else if (opts.inBitmap != null) {
+ // bitmap was reused, ensure density is reset
+ outputBitmap.setDensity(Bitmap.getDefaultDensity());
+ }
+ }
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
index 2b86bfb..d8ead23 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
new file mode 100644
index 0000000..65d1dc5
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets_tab.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 2dca07c..dea86bf 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -305,6 +305,11 @@
renderAndVerify("array_check.xml", "array_check.png");
}
+ @Test
+ public void testAllWidgetsTablet() throws ClassNotFoundException {
+ renderAndVerify("allwidgets.xml", "allwidgets_tab.png", ConfigGenerator.NEXUS_7_2012);
+ }
+
@AfterClass
public static void tearDown() {
sLayoutLibLog = null;
@@ -423,6 +428,16 @@
*/
private void renderAndVerify(String layoutFileName, String goldenFileName)
throws ClassNotFoundException {
+ renderAndVerify(layoutFileName, goldenFileName, ConfigGenerator.NEXUS_5);
+ }
+
+ /**
+ * Create a new rendering session and test that rendering given layout on given device
+ * doesn't throw any exceptions and matches the provided image.
+ */
+ private void renderAndVerify(String layoutFileName, String goldenFileName,
+ ConfigGenerator deviceConfig)
+ throws ClassNotFoundException {
// Create the layout pull parser.
LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" + layoutFileName);
// Create LayoutLibCallback.
@@ -430,7 +445,7 @@
layoutLibCallback.initResources();
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
- SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ SessionParams params = getSessionParams(parser, deviceConfig,
layoutLibCallback, "AppTheme", true, RenderingMode.NORMAL, 22);
renderAndVerify(params, goldenFileName);
}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
index 8e0cec6..34fc726 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java
@@ -126,6 +126,21 @@
.setSoftButtons(true)
.setNavigation(Navigation.NONAV);
+ public static final ConfigGenerator NEXUS_7_2012 = new ConfigGenerator()
+ .setScreenHeight(1280)
+ .setScreenWidth(800)
+ .setXdpi(195)
+ .setYdpi(200)
+ .setOrientation(ScreenOrientation.PORTRAIT)
+ .setDensity(Density.TV)
+ .setRatio(ScreenRatio.NOTLONG)
+ .setSize(ScreenSize.LARGE)
+ .setKeyboard(Keyboard.NOKEY)
+ .setTouchScreen(TouchScreen.FINGER)
+ .setKeyboardState(KeyboardState.SOFT)
+ .setSoftButtons(true)
+ .setNavigation(Navigation.NONAV);
+
private static final String TAG_ATTR = "attr";
private static final String TAG_ENUM = "enum";
private static final String TAG_FLAG = "flag";
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 3ca7590..6e6ad8f 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -166,6 +166,7 @@
"android.content.res.TypedArray#getValueAt",
"android.content.res.TypedArray#obtain",
"android.graphics.BitmapFactory#finishDecode",
+ "android.graphics.BitmapFactory#setDensityFromOptions",
"android.graphics.drawable.GradientDrawable#buildRing",
"android.graphics.Typeface#getSystemFontConfigLocation",
"android.os.Handler#sendMessageAtTime",