diff options
112 files changed, 2950 insertions, 1359 deletions
diff --git a/api/current.txt b/api/current.txt index 8552237ba024..9d61da7f1ab5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -607,6 +607,7 @@ package android { field public static final int fontFamily = 16843692; // 0x10103ac field public static final int fontFeatureSettings = 16843959; // 0x10104b7 field public static final int fontProviderAuthority = 16844114; // 0x1010552 + field public static final int fontProviderPackage = 16844122; // 0x101055a field public static final int fontProviderQuery = 16844115; // 0x1010553 field public static final int fontStyle = 16844081; // 0x1010531 field public static final int fontWeight = 16844083; // 0x1010533 @@ -2811,6 +2812,7 @@ package android.accessibilityservice { method public android.content.pm.ResolveInfo getResolveInfo(); method public java.lang.String getSettingsActivityName(); method public java.lang.String loadDescription(android.content.pm.PackageManager); + method public java.lang.String loadSummary(android.content.pm.PackageManager); method public void writeToParcel(android.os.Parcel, int); field public static final int CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES = 64; // 0x40 field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10 @@ -14088,9 +14090,12 @@ package android.graphics.drawable.shapes { package android.graphics.fonts { public final class FontRequest implements android.os.Parcelable { - ctor public FontRequest(java.lang.String, java.lang.String); + ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String); + ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String, java.util.List<java.util.List<byte[]>>); method public int describeContents(); + method public java.util.List<java.util.List<byte[]>> getCertificates(); method public java.lang.String getProviderAuthority(); + method public java.lang.String getProviderPackage(); method public java.lang.String getQuery(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR; @@ -19634,6 +19639,8 @@ package android.icu.util { field public static final int SHORT_COMMONLY_USED = 6; // 0x6 field public static final int SHORT_GENERIC = 2; // 0x2 field public static final int SHORT_GMT = 4; // 0x4 + field public static final int TIMEZONE_ICU = 0; // 0x0 + field public static final int TIMEZONE_JDK = 1; // 0x1 field public static final android.icu.util.TimeZone UNKNOWN_ZONE; field public static final java.lang.String UNKNOWN_ZONE_ID = "Etc/Unknown"; } @@ -24948,6 +24955,7 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); method public static deprecated boolean setProcessDefaultNetwork(android.net.Network); @@ -24995,6 +25003,7 @@ package android.net { method public void onLinkPropertiesChanged(android.net.Network, android.net.LinkProperties); method public void onLosing(android.net.Network, int); method public void onLost(android.net.Network); + method public void onUnavailable(); } public static abstract interface ConnectivityManager.OnNetworkActiveListener { @@ -38198,6 +38207,7 @@ package android.telecom { method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details); method public void onParentChanged(android.telecom.Call, android.telecom.Call); method public void onPostDialWait(android.telecom.Call, java.lang.String); + method public void onRttInitiationFailure(android.telecom.Call, int); method public void onRttModeChanged(android.telecom.Call, int); method public void onRttRequest(android.telecom.Call, int); method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall); @@ -38464,6 +38474,15 @@ package android.telecom { field public static final int STATE_RINGING = 2; // 0x2 } + public static final class Connection.RttModifyStatus { + ctor public Connection.RttModifyStatus(); + field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2 + field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3 + field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5 + field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1 + field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4 + } + public static abstract class Connection.VideoProvider { ctor public Connection.VideoProvider(); method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities); @@ -38521,9 +38540,9 @@ package android.telecom { method public final android.os.IBinder onBind(android.content.Intent); method public void onConference(android.telecom.Connection, android.telecom.Connection); method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); - method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest); + method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); - method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest); + method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onRemoteConferenceAdded(android.telecom.RemoteConference); method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection); field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService"; diff --git a/api/system-current.txt b/api/system-current.txt index 94825a390ce2..87846c0b14fd 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -719,6 +719,7 @@ package android { field public static final int fontFamily = 16843692; // 0x10103ac field public static final int fontFeatureSettings = 16843959; // 0x10104b7 field public static final int fontProviderAuthority = 16844114; // 0x1010552 + field public static final int fontProviderPackage = 16844122; // 0x101055a field public static final int fontProviderQuery = 16844115; // 0x1010553 field public static final int fontStyle = 16844081; // 0x1010531 field public static final int fontWeight = 16844083; // 0x1010533 @@ -2930,6 +2931,7 @@ package android.accessibilityservice { method public android.content.pm.ResolveInfo getResolveInfo(); method public java.lang.String getSettingsActivityName(); method public java.lang.String loadDescription(android.content.pm.PackageManager); + method public java.lang.String loadSummary(android.content.pm.PackageManager); method public void writeToParcel(android.os.Parcel, int); field public static final int CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES = 64; // 0x40 field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10 @@ -14805,9 +14807,12 @@ package android.graphics.drawable.shapes { package android.graphics.fonts { public final class FontRequest implements android.os.Parcelable { - ctor public FontRequest(java.lang.String, java.lang.String); + ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String); + ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String, java.util.List<java.util.List<byte[]>>); method public int describeContents(); + method public java.util.List<java.util.List<byte[]>> getCertificates(); method public java.lang.String getProviderAuthority(); + method public java.lang.String getProviderPackage(); method public java.lang.String getQuery(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR; @@ -21073,6 +21078,8 @@ package android.icu.util { field public static final int SHORT_COMMONLY_USED = 6; // 0x6 field public static final int SHORT_GENERIC = 2; // 0x2 field public static final int SHORT_GMT = 4; // 0x4 + field public static final int TIMEZONE_ICU = 0; // 0x0 + field public static final int TIMEZONE_JDK = 1; // 0x1 field public static final android.icu.util.TimeZone UNKNOWN_ZONE; field public static final java.lang.String UNKNOWN_ZONE_ID = "Etc/Unknown"; } @@ -26722,7 +26729,12 @@ package android.metrics { ctor public LogMaker(int); ctor public LogMaker(java.lang.Object[]); method public android.metrics.LogMaker addTaggedData(int, java.lang.Object); + method public android.metrics.LogMaker clearCategory(); + method public android.metrics.LogMaker clearPackageName(); + method public android.metrics.LogMaker clearSubtype(); method public android.metrics.LogMaker clearTaggedData(int); + method public android.metrics.LogMaker clearTimestamp(); + method public android.metrics.LogMaker clearType(); method public void deserialize(java.lang.Object[]); method public int getCategory(); method public long getCounterBucket(); @@ -27041,6 +27053,7 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); method public static deprecated boolean setProcessDefaultNetwork(android.net.Network); @@ -27094,6 +27107,7 @@ package android.net { method public void onLinkPropertiesChanged(android.net.Network, android.net.LinkProperties); method public void onLosing(android.net.Network, int); method public void onLost(android.net.Network); + method public void onUnavailable(); } public static abstract interface ConnectivityManager.OnNetworkActiveListener { @@ -41330,6 +41344,7 @@ package android.telecom { method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details); method public void onParentChanged(android.telecom.Call, android.telecom.Call); method public void onPostDialWait(android.telecom.Call, java.lang.String); + method public void onRttInitiationFailure(android.telecom.Call, int); method public void onRttModeChanged(android.telecom.Call, int); method public void onRttRequest(android.telecom.Call, int); method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall); @@ -41607,6 +41622,15 @@ package android.telecom { field public static final int STATE_RINGING = 2; // 0x2 } + public static final class Connection.RttModifyStatus { + ctor public Connection.RttModifyStatus(); + field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2 + field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3 + field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5 + field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1 + field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4 + } + public static abstract class Connection.VideoProvider { ctor public Connection.VideoProvider(); method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities); @@ -41664,9 +41688,9 @@ package android.telecom { method public final android.os.IBinder onBind(android.content.Intent); method public void onConference(android.telecom.Connection, android.telecom.Connection); method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); - method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest); + method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); - method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest); + method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onRemoteConferenceAdded(android.telecom.RemoteConference); method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection); field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService"; diff --git a/api/test-current.txt b/api/test-current.txt index b92a04ec5209..cce60245ee31 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -607,6 +607,7 @@ package android { field public static final int fontFamily = 16843692; // 0x10103ac field public static final int fontFeatureSettings = 16843959; // 0x10104b7 field public static final int fontProviderAuthority = 16844114; // 0x1010552 + field public static final int fontProviderPackage = 16844122; // 0x101055a field public static final int fontProviderQuery = 16844115; // 0x1010553 field public static final int fontStyle = 16844081; // 0x1010531 field public static final int fontWeight = 16844083; // 0x1010533 @@ -2811,6 +2812,7 @@ package android.accessibilityservice { method public android.content.pm.ResolveInfo getResolveInfo(); method public java.lang.String getSettingsActivityName(); method public java.lang.String loadDescription(android.content.pm.PackageManager); + method public java.lang.String loadSummary(android.content.pm.PackageManager); method public void writeToParcel(android.os.Parcel, int); field public static final int CAPABILITY_CAN_CAPTURE_FINGERPRINT_GESTURES = 64; // 0x40 field public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 16; // 0x10 @@ -14127,9 +14129,12 @@ package android.graphics.drawable.shapes { package android.graphics.fonts { public final class FontRequest implements android.os.Parcelable { - ctor public FontRequest(java.lang.String, java.lang.String); + ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String); + ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String, java.util.List<java.util.List<byte[]>>); method public int describeContents(); + method public java.util.List<java.util.List<byte[]>> getCertificates(); method public java.lang.String getProviderAuthority(); + method public java.lang.String getProviderPackage(); method public java.lang.String getQuery(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR; @@ -19673,6 +19678,8 @@ package android.icu.util { field public static final int SHORT_COMMONLY_USED = 6; // 0x6 field public static final int SHORT_GENERIC = 2; // 0x2 field public static final int SHORT_GMT = 4; // 0x4 + field public static final int TIMEZONE_ICU = 0; // 0x0 + field public static final int TIMEZONE_JDK = 1; // 0x1 field public static final android.icu.util.TimeZone UNKNOWN_ZONE; field public static final java.lang.String UNKNOWN_ZONE_ID = "Etc/Unknown"; } @@ -25045,6 +25052,7 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); method public static deprecated boolean setProcessDefaultNetwork(android.net.Network); @@ -25092,6 +25100,7 @@ package android.net { method public void onLinkPropertiesChanged(android.net.Network, android.net.LinkProperties); method public void onLosing(android.net.Network, int); method public void onLost(android.net.Network); + method public void onUnavailable(); } public static abstract interface ConnectivityManager.OnNetworkActiveListener { @@ -38383,6 +38392,7 @@ package android.telecom { method public void onDetailsChanged(android.telecom.Call, android.telecom.Call.Details); method public void onParentChanged(android.telecom.Call, android.telecom.Call); method public void onPostDialWait(android.telecom.Call, java.lang.String); + method public void onRttInitiationFailure(android.telecom.Call, int); method public void onRttModeChanged(android.telecom.Call, int); method public void onRttRequest(android.telecom.Call, int); method public void onRttStatusChanged(android.telecom.Call, boolean, android.telecom.Call.RttCall); @@ -38649,6 +38659,15 @@ package android.telecom { field public static final int STATE_RINGING = 2; // 0x2 } + public static final class Connection.RttModifyStatus { + ctor public Connection.RttModifyStatus(); + field public static final int SESSION_MODIFY_REQUEST_FAIL = 2; // 0x2 + field public static final int SESSION_MODIFY_REQUEST_INVALID = 3; // 0x3 + field public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; // 0x5 + field public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; // 0x1 + field public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; // 0x4 + } + public static abstract class Connection.VideoProvider { ctor public Connection.VideoProvider(); method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities); @@ -38706,9 +38725,9 @@ package android.telecom { method public final android.os.IBinder onBind(android.content.Intent); method public void onConference(android.telecom.Connection, android.telecom.Connection); method public android.telecom.Connection onCreateIncomingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); - method public void onCreateIncomingConnectionFailed(android.telecom.ConnectionRequest); + method public void onCreateIncomingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public android.telecom.Connection onCreateOutgoingConnection(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); - method public void onCreateOutgoingConnectionFailed(android.telecom.ConnectionRequest); + method public void onCreateOutgoingConnectionFailed(android.telecom.PhoneAccountHandle, android.telecom.ConnectionRequest); method public void onRemoteConferenceAdded(android.telecom.RemoteConference); method public void onRemoteExistingConnectionAdded(android.telecom.RemoteConnection); field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.ConnectionService"; diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 19d1a7d899c9..5937dd951dc4 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -69,10 +69,10 @@ import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; * @attr ref android.R.styleable#AccessibilityService_canRequestTouchExplorationMode * @attr ref android.R.styleable#AccessibilityService_canRetrieveWindowContent * @attr ref android.R.styleable#AccessibilityService_description + * @attr ref android.R.styleable#AccessibilityService_summary * @attr ref android.R.styleable#AccessibilityService_notificationTimeout * @attr ref android.R.styleable#AccessibilityService_packageNames * @attr ref android.R.styleable#AccessibilityService_settingsActivity - * * @see AccessibilityService * @see android.view.accessibility.AccessibilityEvent * @see android.view.accessibility.AccessibilityManager @@ -431,6 +431,16 @@ public class AccessibilityServiceInfo implements Parcelable { private int mCapabilities; /** + * Resource id of the summary of the accessibility service. + */ + private int mSummaryResId; + + /** + * Non-localized summary of the accessibility service. + */ + private String mNonLocalizedSummary; + + /** * Resource id of the description of the accessibility service. */ private int mDescriptionResId; @@ -544,6 +554,15 @@ public class AccessibilityServiceInfo implements Parcelable { mNonLocalizedDescription = nonLocalizedDescription.toString().trim(); } } + peekedValue = asAttributes.peekValue( + com.android.internal.R.styleable.AccessibilityService_summary); + if (peekedValue != null) { + mSummaryResId = peekedValue.resourceId; + CharSequence nonLocalizedSummary = peekedValue.coerceToString(); + if (nonLocalizedSummary != null) { + mNonLocalizedSummary = nonLocalizedSummary.toString().trim(); + } + } asAttributes.recycle(); } catch (NameNotFoundException e) { throw new XmlPullParserException( "Unable to create context for: " @@ -669,6 +688,27 @@ public class AccessibilityServiceInfo implements Parcelable { } /** + * The localized summary of the accessibility service. + * <p> + * <strong>Statically set from + * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> + * </p> + * @return The localized summary. + */ + public String loadSummary(PackageManager packageManager) { + if (mSummaryResId == 0) { + return mNonLocalizedSummary; + } + ServiceInfo serviceInfo = mResolveInfo.serviceInfo; + CharSequence summary = packageManager.getText(serviceInfo.packageName, + mSummaryResId, serviceInfo.applicationInfo); + if (summary != null) { + return summary.toString().trim(); + } + return null; + } + + /** * Gets the non-localized description of the accessibility service. * <p> * <strong>Statically set from @@ -726,6 +766,8 @@ public class AccessibilityServiceInfo implements Parcelable { parcel.writeParcelable(mResolveInfo, 0); parcel.writeString(mSettingsActivityName); parcel.writeInt(mCapabilities); + parcel.writeInt(mSummaryResId); + parcel.writeString(mNonLocalizedSummary); parcel.writeInt(mDescriptionResId); parcel.writeString(mNonLocalizedDescription); } @@ -740,6 +782,8 @@ public class AccessibilityServiceInfo implements Parcelable { mResolveInfo = parcel.readParcelable(null); mSettingsActivityName = parcel.readString(); mCapabilities = parcel.readInt(); + mSummaryResId = parcel.readInt(); + mNonLocalizedSummary = parcel.readString(); mDescriptionResId = parcel.readInt(); mNonLocalizedDescription = parcel.readString(); } @@ -790,6 +834,8 @@ public class AccessibilityServiceInfo implements Parcelable { stringBuilder.append(", "); stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName); stringBuilder.append(", "); + stringBuilder.append("summary: ").append(mNonLocalizedSummary); + stringBuilder.append(", "); appendCapabilities(stringBuilder, mCapabilities); return stringBuilder.toString(); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e0dc10daf64d..d6306e001516 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1465,7 +1465,7 @@ public class Intent implements Parcelable, Cloneable { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SHOW_KEYBOARD_SHORTCUTS = - "android.intent.action.SHOW_KEYBOARD_SHORTCUTS"; + "com.android.intent.action.SHOW_KEYBOARD_SHORTCUTS"; /** * Activity Action: Dismiss the Keyboard Shortcuts Helper screen. @@ -1475,7 +1475,7 @@ public class Intent implements Parcelable, Cloneable { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DISMISS_KEYBOARD_SHORTCUTS = - "android.intent.action.DISMISS_KEYBOARD_SHORTCUTS"; + "com.android.intent.action.DISMISS_KEYBOARD_SHORTCUTS"; /** * Activity Action: Show settings for managing network data usage of a @@ -3124,7 +3124,7 @@ public class Intent implements Parcelable, Cloneable { * @hide */ public static final String ACTION_SHOW_BRIGHTNESS_DIALOG = - "android.intent.action.SHOW_BRIGHTNESS_DIALOG"; + "com.android.intent.action.SHOW_BRIGHTNESS_DIALOG"; /** * Broadcast Action: A global button was pressed. Includes a single diff --git a/core/java/android/content/pm/SELinuxUtil.java b/core/java/android/content/pm/SELinuxUtil.java index 55b5e297e18f..025c0fe30877 100644 --- a/core/java/android/content/pm/SELinuxUtil.java +++ b/core/java/android/content/pm/SELinuxUtil.java @@ -32,11 +32,10 @@ public final class SELinuxUtil { /** @hide */ public static String assignSeinfoUser(PackageUserState userState) { - String seInfo = ""; - if (userState.instantApp) - seInfo += INSTANT_APP_STR; - seInfo += COMPLETE_STR; - return seInfo; + if (userState.instantApp) { + return INSTANT_APP_STR + COMPLETE_STR; + } + return COMPLETE_STR; } } diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java index 3f8f90ebf054..50fc3443f5a6 100644 --- a/core/java/android/content/res/FontResourcesParser.java +++ b/core/java/android/content/res/FontResourcesParser.java @@ -67,13 +67,14 @@ public class FontResourcesParser { AttributeSet attrs = Xml.asAttributeSet(parser); TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily); String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority); + String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage); String query = array.getString(R.styleable.FontFamily_fontProviderQuery); array.recycle(); - if (authority != null && query != null) { + if (authority != null && providerPackage != null && query != null) { while (parser.next() != XmlPullParser.END_TAG) { skip(parser); } - return new FontConfig.Family(authority, query); + return new FontConfig.Family(authority, providerPackage, query); } List<FontConfig.Font> fonts = new ArrayList<>(); while (parser.next() != XmlPullParser.END_TAG) { diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java index 0ee25744488c..612b1356e306 100644 --- a/core/java/android/metrics/LogMaker.java +++ b/core/java/android/metrics/LogMaker.java @@ -60,26 +60,51 @@ public class LogMaker { return this; } + public LogMaker clearCategory() { + entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_CATEGORY); + return this; + } + public LogMaker setType(int type) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE, type); return this; } + public LogMaker clearType() { + entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_TYPE); + return this; + } + public LogMaker setSubtype(int subtype) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE, subtype); return this; } + public LogMaker clearSubtype() { + entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_SUBTYPE); + return this; + } + public LogMaker setTimestamp(long timestamp) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP, timestamp); return this; } + public LogMaker clearTimestamp() { + entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_TIMESTAMP); + return this; + } + public LogMaker setPackageName(String packageName) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME, packageName); return this; } + public LogMaker clearPackageName() { + entries.remove(MetricsEvent.RESERVED_FOR_LOGBUILDER_PACKAGENAME); + return this; + } + public LogMaker setCounterName(String name) { entries.put(MetricsEvent.RESERVED_FOR_LOGBUILDER_NAME, name); return this; diff --git a/core/java/android/metrics/MetricsReader.java b/core/java/android/metrics/MetricsReader.java index 079c2c93c05b..dd8a74d1bfae 100644 --- a/core/java/android/metrics/MetricsReader.java +++ b/core/java/android/metrics/MetricsReader.java @@ -16,10 +16,15 @@ package android.metrics; import android.annotation.SystemApi; +import android.util.EventLog; +import android.util.EventLog.Event; +import android.util.Log; -import com.android.internal.logging.legacy.LegacyConversionLogger; -import com.android.internal.logging.legacy.EventLogCollector; +import com.android.internal.logging.MetricsLogger; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedList; import java.util.Queue; /** @@ -28,41 +33,87 @@ import java.util.Queue; */ @SystemApi public class MetricsReader { - private EventLogCollector mReader; - private Queue<LogMaker> mEventQueue; + private Queue<LogMaker> mEventQueue = new LinkedList<>(); private long mLastEventMs; private long mCheckpointMs; + private int[] LOGTAGS = { MetricsLogger.LOGTAG }; - /** Open a new session and start reading logs. + /** + * Read the available logs into a new session. * - * Starts reading from the oldest log not already read by this reader object. - * On first invocation starts from the oldest available log ion the system. + * The session will contain events starting from the oldest available + * log on the system up to the most recent at the time of this call. + * + * A call to {@link #checkpoint()} will cause the session to contain + * only events that occured after that call. + * + * This call will not return until the system buffer overflows the + * specified timestamp. If the specified timestamp is 0, then the + * call will return immediately since any logs 1970 have already been + * overwritten (n.b. if the underlying system has the capability to + * store many decades of system logs, this call may fail in + * interesting ways.) + * + * @param horizonMs block until this timestamp is overwritten, 0 for non-blocking read. */ - public void read(long startMs) { - EventLogCollector reader = EventLogCollector.getInstance(); - LegacyConversionLogger logger = new LegacyConversionLogger(); - mLastEventMs = reader.collect(logger, startMs); - mEventQueue = logger.getEvents(); + public void read(long horizonMs) { + ArrayList<Event> nativeEvents = new ArrayList<>(); + try { + EventLog.readEventsOnWrapping(LOGTAGS, horizonMs, nativeEvents); + } catch (IOException e) { + e.printStackTrace(); + } + mEventQueue.clear(); + for (EventLog.Event event : nativeEvents) { + final long eventTimestampMs = event.getTimeNanos() / 1000000; + if (eventTimestampMs > mCheckpointMs) { + Object data = event.getData(); + Object[] objects; + if (data instanceof Object[]) { + objects = (Object[]) data; + } else { + // wrap scalar objects + objects = new Object[1]; + objects[0] = data; + } + mEventQueue.add(new LogMaker(objects) + .setTimestamp(eventTimestampMs)); + mLastEventMs = eventTimestampMs; + } + } } + /** Cause this session to only contain events that occur after this call. */ public void checkpoint() { + // read the log to find the most recent event. read(0L); + // any queued event is now too old, so drop them. + mEventQueue.clear(); mCheckpointMs = mLastEventMs; - mEventQueue = null; } + /** + * Rewind the session to the beginning of time and read all available logs. + * + * A prior call to {@link #checkpoint()} will cause the reader to ignore + * any event with a timestamp before the time of that call. + * + * The underlying log buffer is live: between calls to {@link #reset()}, older + * events may be lost from the beginning of the session, and new events may + * appear at the end. + */ public void reset() { - read(mCheckpointMs); + read(0l); } /* Does the current log session have another entry? */ public boolean hasNext() { - return mEventQueue == null ? false : !mEventQueue.isEmpty(); + return !mEventQueue.isEmpty(); } - /* Next entry in the current log session. */ + /* Return the next entry in the current log session. */ public LogMaker next() { - return mEventQueue == null ? null : mEventQueue.remove(); + return mEventQueue.poll(); } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 1259de602f29..14333f732d14 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1069,26 +1069,6 @@ public class ConnectivityManager { } /** - * Request that this callback be invoked at ConnectivityService's earliest - * convenience with the current satisfying network's LinkProperties. - * If no such network exists no callback invocation is performed. - * - * The callback must have been registered with #requestNetwork() or - * #registerDefaultNetworkCallback(); callbacks registered with - * registerNetworkCallback() are not specific to any particular Network so - * do not cause any updates. - * - * @hide - */ - public void requestLinkProperties(NetworkCallback networkCallback) { - try { - mService.requestLinkProperties(networkCallback.networkRequest); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This * will return {@code null} if the network is unknown. * <p>This method requires the caller to hold the permission @@ -1106,26 +1086,6 @@ public class ConnectivityManager { } /** - * Request that this callback be invoked at ConnectivityService's earliest - * convenience with the current satisfying network's NetworkCapabilities. - * If no such network exists no callback invocation is performed. - * - * The callback must have been registered with #requestNetwork() or - * #registerDefaultNetworkCallback(); callbacks registered with - * registerNetworkCallback() are not specific to any particular Network so - * do not cause any updates. - * - * @hide - */ - public void requestNetworkCapabilities(NetworkCallback networkCallback) { - try { - mService.requestNetworkCapabilities(networkCallback.networkRequest); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Gets the URL that should be used for resolving whether a captive portal is present. * 1. This URL should respond with a 204 response to a GET request to indicate no captive * portal is present. @@ -2676,10 +2636,12 @@ public class ConnectivityManager { public void onLost(Network network) {} /** - * Called if no network is found in the given timeout time. If no timeout is given, - * this will not be called. The associated {@link NetworkRequest} will have already - * been removed and released, as if {@link #unregisterNetworkCallback} had been called. - * @hide + * Called if no network is found in the timeout time specified in + * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} call. This callback is not + * called for the version of {@link #requestNetwork(NetworkRequest, NetworkCallback)} + * without timeout. When this callback is invoked the associated + * {@link NetworkRequest} will have already been removed and released, as if + * {@link #unregisterNetworkCallback(NetworkCallback)} had been called. */ public void onUnavailable() {} @@ -2962,7 +2924,9 @@ public class ConnectivityManager { * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. * * This {@link NetworkRequest} will live until released via - * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A + * version of the method which takes a timeout is + * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}. * Status of the request can be followed by listening to the various * callbacks described in {@link NetworkCallback}. The {@link Network} * can be used to direct traffic to the network. @@ -2995,7 +2959,9 @@ public class ConnectivityManager { * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}. * * This {@link NetworkRequest} will live until released via - * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. + * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A + * version of the method which takes a timeout is + * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)}. * Status of the request can be followed by listening to the various * callbacks described in {@link NetworkCallback}. The {@link Network} * can be used to direct traffic to the network. @@ -3029,13 +2995,25 @@ public class ConnectivityManager { } /** + * Note: this is a deprecated version of + * {@link #requestNetwork(NetworkRequest, int, NetworkCallback)} - please transition code to use + * the unhidden version of the function. + * TODO: replace all callers with the new version of the API + * * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited * by a timeout. * - * This function behaves identically to the non-timedout version, but if a suitable - * network is not found within the given time (in milliseconds) the - * {@link NetworkCallback#onUnavailable()} callback is called. The request must - * still be released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)}. + * This function behaves identically to the non-timed-out version + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network + * is not found within the given time (in milliseconds) the + * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be + * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does + * not have to be released if timed-out (it is automatically released). Unregistering a + * request that timed out is not an error. + * + * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small + * timeout) - the {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided + * for that purpose. Calling this method will attempt to bring up the requested network. * * <p>This method requires the caller to hold either the * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission @@ -3043,15 +3021,56 @@ public class ConnectivityManager { * {@link android.provider.Settings.System#canWrite}.</p> * * @param request {@link NetworkRequest} describing this request. - * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note - * the callback must not be shared - it uniquely specifies this request. - * The callback is invoked on the default internal Handler. + * @param networkCallback The callbacks to be utilized for this request. Note + * the callbacks must not be shared - they uniquely specify + * this request. * @param timeoutMs The time in milliseconds to attempt looking for a suitable network - * before {@link NetworkCallback#onUnavailable()} is called. + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). * @hide */ public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback, int timeoutMs) { + if (timeoutMs <= 0) { + throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs); + } + int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities); + requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler()); + } + + /** + * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited + * by a timeout. + * + * This function behaves identically to the non-timed-out version + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, but if a suitable network + * is not found within the given time (in milliseconds) the + * {@link NetworkCallback#onUnavailable()} callback is called. The request can still be + * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does + * not have to be released if timed-out (it is automatically released). Unregistering a + * request that timed out is not an error. + * + * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small + * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided + * for that purpose. Calling this method will attempt to bring up the requested network. + * + * <p>This method requires the caller to hold either the + * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission + * or the ability to modify system settings as determined by + * {@link android.provider.Settings.System#canWrite}.</p> + * + * @param request {@link NetworkRequest} describing this request. + * @param timeoutMs The time in milliseconds to attempt looking for a suitable network + * before {@link NetworkCallback#onUnavailable()} is called. The timeout must + * be a positive value (i.e. >0). + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. + */ + public void requestNetwork(NetworkRequest request, int timeoutMs, + NetworkCallback networkCallback) { + if (timeoutMs <= 0) { + throw new IllegalArgumentException("Non-positive timeoutMs: " + timeoutMs); + } int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities); requestNetwork(request, networkCallback, timeoutMs, legacyType, getDefaultHandler()); } @@ -3063,8 +3082,14 @@ public class ConnectivityManager { * * This function behaves identically to the non-timedout version, but if a suitable * network is not found within the given time (in milliseconds) the - * {@link NetworkCallback#onUnavailable} callback is called. The request must - * still be released normally by calling {@link unregisterNetworkCallback(NetworkCallback)}. + * {@link NetworkCallback#onUnavailable} callback is called. The request can still be + * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does + * not have to be released if timed-out (it is automatically released). Unregistering a + * request that timed out is not an error. + * + * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small + * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided + * for that purpose. Calling this method will attempt to bring up the requested network. * * <p>This method requires the caller to hold either the * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission @@ -3072,16 +3097,19 @@ public class ConnectivityManager { * {@link android.provider.Settings.System#canWrite}.</p> * * @param request {@link NetworkRequest} describing this request. - * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note - * the callback must not be shared - it uniquely specifies this request. * @param timeoutMs The time in milliseconds to attempt looking for a suitable network * before {@link NetworkCallback#onUnavailable} is called. + * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note + * the callback must not be shared - it uniquely specifies this request. * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. * * @hide */ - public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback, - int timeoutMs, Handler handler) { + public void requestNetwork(NetworkRequest request, int timeoutMs, + NetworkCallback networkCallback, Handler handler) { + if (timeoutMs <= 0) { + throw new IllegalArgumentException("Non-positive timeoutMs"); + } int legacyType = inferLegacyTypeForNetworkCapabilities(request.networkCapabilities); CallbackHandler cbHandler = new CallbackHandler(handler); requestNetwork(request, networkCallback, timeoutMs, legacyType, cbHandler); diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 117fa0b476f4..425e49407827 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -156,8 +156,6 @@ interface IConnectivityManager void pendingListenForNetwork(in NetworkCapabilities networkCapabilities, in PendingIntent operation); - void requestLinkProperties(in NetworkRequest networkRequest); - void requestNetworkCapabilities(in NetworkRequest networkRequest); void releaseNetworkRequest(in NetworkRequest networkRequest); void setAcceptUnvalidated(in Network network, boolean accept, boolean always); diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java index bd3231464ccf..2c9ce3f99285 100644 --- a/core/java/android/nfc/NdefRecord.java +++ b/core/java/android/nfc/NdefRecord.java @@ -839,7 +839,7 @@ public final class NdefRecord implements Parcelable { if (cf && !inChunk) { // first chunk - if (typeLength == 0) { + if (typeLength == 0 && tnf != NdefRecord.TNF_UNKNOWN) { throw new FormatException("expected non-zero type length in first chunk"); } chunks.clear(); diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java index 90e710f12c08..96dd76b8fe72 100644 --- a/core/java/android/provider/FontsContract.java +++ b/core/java/android/provider/FontsContract.java @@ -19,9 +19,12 @@ import android.app.ActivityThread; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; +import android.content.pm.Signature; import android.database.Cursor; +import android.graphics.Typeface; import android.graphics.fonts.FontRequest; import android.graphics.fonts.FontResult; import android.net.Uri; @@ -34,9 +37,13 @@ import android.os.ResultReceiver; import android.util.Log; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.io.FileNotFoundException; import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * Utility class to deal with Font ContentProviders. @@ -107,6 +114,13 @@ public class FontsContract { mPackageManager = mContext.getPackageManager(); } + /** @hide */ + @VisibleForTesting + public FontsContract(Context context, PackageManager packageManager) { + mContext = context; + mPackageManager = packageManager; + } + // We use a background thread to post the content resolving work for all requests on. This // thread should be quit/stopped after all requests are done. private final Runnable mReplaceDispatcherThreadRunnable = new Runnable() { @@ -133,31 +147,79 @@ public class FontsContract { mHandler = new Handler(mThread.getLooper()); } mHandler.post(() -> { - String providerAuthority = request.getProviderAuthority(); - // TODO: Implement cert checking for non-system apps - ProviderInfo providerInfo = mPackageManager.resolveContentProvider( - providerAuthority, PackageManager.MATCH_SYSTEM_ONLY); + ProviderInfo providerInfo = getProvider(request); if (providerInfo == null) { receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null); return; } - Bundle result = getFontFromProvider(request, receiver, providerInfo); - if (result == null) { - receiver.send(RESULT_CODE_FONT_NOT_FOUND, null); - return; - } - receiver.send(RESULT_CODE_OK, result); + getFontFromProvider(request, receiver, providerInfo.authority); }); mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable); mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS); } } - private Bundle getFontFromProvider(FontRequest request, ResultReceiver receiver, - ProviderInfo providerInfo) { + /** @hide */ + @VisibleForTesting + public ProviderInfo getProvider(FontRequest request) { + String providerAuthority = request.getProviderAuthority(); + ProviderInfo info = mPackageManager.resolveContentProvider(providerAuthority, 0); + if (info == null) { + Log.e(TAG, "Can't find content provider " + providerAuthority); + return null; + } + + if (!info.packageName.equals(request.getProviderPackage())) { + Log.e(TAG, "Found content provider " + providerAuthority + ", but package was not " + + request.getProviderPackage()); + return null; + } + // Trust system apps without signature checks + if (info.applicationInfo.isSystemApp()) { + return info; + } + + Set<byte[]> signatures; + try { + PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName, + PackageManager.GET_SIGNATURES); + signatures = convertToSet(packageInfo.signatures); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Can't find content provider " + providerAuthority, e); + return null; + } + List<List<byte[]>> requestCertificatesList = request.getCertificates(); + for (int i = 0; i < requestCertificatesList.size(); ++i) { + final Set<byte[]> requestCertificates = convertToSet(requestCertificatesList.get(i)); + if (signatures.equals(requestCertificates)) { + return info; + } + } + Log.e(TAG, "Certificates don't match for given provider " + providerAuthority); + return null; + } + + private Set<byte[]> convertToSet(Signature[] signatures) { + Set<byte[]> shas = new HashSet<>(); + for (int i = 0; i < signatures.length; ++i) { + shas.add(signatures[i].toByteArray()); + } + return shas; + } + + private Set<byte[]> convertToSet(List<byte[]> certs) { + Set<byte[]> shas = new HashSet<>(); + shas.addAll(certs); + return shas; + } + + /** @hide */ + @VisibleForTesting + public void getFontFromProvider(FontRequest request, ResultReceiver receiver, + String authority) { ArrayList<FontResult> result = null; Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(providerInfo.authority) + .authority(authority) .build(); try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.STYLE }, @@ -176,13 +238,16 @@ public class FontsContract { try { ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(fileUri, "r"); - final int ttcIndex = cursor.getInt(ttcIndexColumnIndex); - final String variationSettings = cursor.getString(vsColumnIndex); - final int style = cursor.getInt(styleColumnIndex); + final int ttcIndex = ttcIndexColumnIndex != -1 + ? cursor.getInt(ttcIndexColumnIndex) : 0; + final String variationSettings = vsColumnIndex != -1 + ? cursor.getString(vsColumnIndex) : null; + final int style = styleColumnIndex != -1 + ? cursor.getInt(styleColumnIndex) : Typeface.NORMAL; result.add(new FontResult(pfd, ttcIndex, variationSettings, style)); } catch (FileNotFoundException e) { Log.e(TAG, "FileNotFoundException raised when interacting with content " - + "provider " + providerInfo.authority, e); + + "provider " + authority, e); } } } @@ -190,8 +255,9 @@ public class FontsContract { if (result != null && !result.isEmpty()) { Bundle bundle = new Bundle(); bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result); - return bundle; + receiver.send(RESULT_CODE_OK, bundle); + return; } - return null; + receiver.send(RESULT_CODE_FONT_NOT_FOUND, null); } } diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 82e44dc86f89..1087851c853f 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -403,6 +403,7 @@ public final class FontConfig implements Parcelable { private final String mLanguage; private final String mVariant; private final String mProviderAuthority; + private final String mProviderPackage; private final String mQuery; public Family(String name, List<Font> fonts, String language, String variant) { @@ -411,18 +412,20 @@ public final class FontConfig implements Parcelable { mLanguage = language; mVariant = variant; mProviderAuthority = null; + mProviderPackage = null; mQuery = null; } /** * @hide */ - public Family(String providerAuthority, String query) { + public Family(String providerAuthority, String providerPackage, String query) { mName = null; mFonts = null; mLanguage = null; mVariant = null; mProviderAuthority = providerAuthority; + mProviderPackage = providerPackage; mQuery = query; } @@ -435,6 +438,7 @@ public final class FontConfig implements Parcelable { mFonts.add(new Font(origin.mFonts.get(i))); } mProviderAuthority = origin.mProviderAuthority; + mProviderPackage = origin.mProviderPackage; mQuery = origin.mQuery; } @@ -476,6 +480,13 @@ public final class FontConfig implements Parcelable { /** * @hide */ + public String getProviderPackage() { + return mProviderPackage; + } + + /** + * @hide + */ public String getQuery() { return mQuery; } @@ -498,6 +509,11 @@ public final class FontConfig implements Parcelable { mProviderAuthority = null; } if (in.readInt() == 1) { + mProviderPackage = in.readString(); + } else { + mProviderPackage = null; + } + if (in.readInt() == 1) { mQuery = in.readString(); } else { mQuery = null; @@ -517,6 +533,10 @@ public final class FontConfig implements Parcelable { if (mProviderAuthority != null) { out.writeString(mProviderAuthority); } + out.writeInt(mProviderPackage == null ? 0 : 1); + if (mProviderPackage != null) { + out.writeString(mProviderPackage); + } out.writeInt(mQuery == null ? 0 : 1); if (mQuery != null) { out.writeString(mQuery); diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java index 80ec03e70cb7..c2508a6c92ef 100644 --- a/core/java/android/text/Hyphenator.java +++ b/core/java/android/text/Hyphenator.java @@ -42,13 +42,24 @@ public class Hyphenator { private static String TAG = "Hyphenator"; + // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but + // that appears too small. + private static final int INDIC_MIN_PREFIX = 2; + private static final int INDIC_MIN_SUFFIX = 2; + private final static Object sLock = new Object(); @GuardedBy("sLock") final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>(); + // Reasonable enough values for cases where we have no hyphenation patterns but may be able to + // do some automatic hyphenation based on characters. These values would be used very rarely. + private static final int DEFAULT_MIN_PREFIX = 2; + private static final int DEFAULT_MIN_SUFFIX = 2; final static Hyphenator sEmptyHyphenator = - new Hyphenator(StaticLayout.nLoadHyphenator(null, 0), null); + new Hyphenator(StaticLayout.nLoadHyphenator( + null, 0, DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX), + null); final private long mNativePtr; @@ -111,15 +122,26 @@ public class Hyphenator { return sEmptyHyphenator; } - private static Hyphenator loadHyphenator(String languageTag) { - String patternFilename = "hyph-" + languageTag.toLowerCase(Locale.US) + ".hyb"; + private static class HyphenationData { + final String mLanguageTag; + final int mMinPrefix, mMinSuffix; + HyphenationData(String languageTag, int minPrefix, int minSuffix) { + this.mLanguageTag = languageTag; + this.mMinPrefix = minPrefix; + this.mMinSuffix = minSuffix; + } + } + + private static Hyphenator loadHyphenator(HyphenationData data) { + String patternFilename = "hyph-" + data.mLanguageTag.toLowerCase(Locale.US) + ".hyb"; File patternFile = new File(getSystemHyphenatorLocation(), patternFilename); try { RandomAccessFile f = new RandomAccessFile(patternFile, "r"); try { FileChannel fc = f.getChannel(); MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); - long nativePtr = StaticLayout.nLoadHyphenator(buf, 0); + long nativePtr = StaticLayout.nLoadHyphenator( + buf, 0, data.mMinPrefix, data.mMinSuffix); return new Hyphenator(nativePtr, buf); } finally { f.close(); @@ -176,6 +198,46 @@ public class Hyphenator { {"wal", "und-Ethi"}, // Wolaytta }; + private static final HyphenationData[] AVAILABLE_LANGUAGES = { + new HyphenationData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Assamese + new HyphenationData("bg", 2, 2), // Bulgarian + new HyphenationData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Bengali + new HyphenationData("cu", 1, 2), // Church Slavonic + new HyphenationData("cy", 2, 3), // Welsh + new HyphenationData("da", 2, 2), // Danish + new HyphenationData("de-1901", 2, 2), // German 1901 orthography + new HyphenationData("de-1996", 2, 2), // German 1996 orthography + new HyphenationData("de-CH-1901", 2, 2), // Swiss High German 1901 orthography + new HyphenationData("en-GB", 2, 3), // British English + new HyphenationData("en-US", 2, 3), // American English + new HyphenationData("es", 2, 2), // Spanish + new HyphenationData("et", 2, 3), // Estonian + new HyphenationData("eu", 2, 2), // Basque + new HyphenationData("fr", 2, 3), // French + new HyphenationData("ga", 2, 3), // Irish + new HyphenationData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Gujarati + new HyphenationData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Hindi + new HyphenationData("hr", 2, 2), // Croatian + new HyphenationData("hu", 2, 2), // Hungarian + // texhyphen sources say Armenian may be (1, 2), but that it needs confirmation. + // Going with a more conservative value of (2, 2) for now. + new HyphenationData("hy", 2, 2), // Armenian + new HyphenationData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Kannada + new HyphenationData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Malayalam + new HyphenationData("mn-Cyrl", 2, 2), // Mongolian in Cyrillic script + new HyphenationData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Marathi + new HyphenationData("nb", 2, 2), // Norwegian Bokmål + new HyphenationData("nn", 2, 2), // Norwegian Nynorsk + new HyphenationData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Oriya + new HyphenationData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Punjabi + new HyphenationData("pt", 2, 3), // Portuguese + new HyphenationData("sl", 2, 2), // Slovenian + new HyphenationData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Tamil + new HyphenationData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Telugu + new HyphenationData("tk", 2, 2), // Turkmen + new HyphenationData("und-Ethi", 1, 1), // Any language in Ethiopic script + }; + /** * Load hyphenation patterns at initialization time. We want to have patterns * for all locales loaded and ready to use so we don't have to do any file IO @@ -186,46 +248,11 @@ public class Hyphenator { public static void init() { sMap.put(null, null); - // TODO: replace this with a discovery-based method that looks into /system/usr/hyphen-data - String[] availableLanguages = { - "as", - "bg", - "bn", - "cu", - "cy", - "da", - "de-1901", "de-1996", "de-CH-1901", - "en-GB", "en-US", - "es", - "et", - "eu", - "fr", - "ga", - "gu", - "hi", - "hr", - "hu", - "hy", - "kn", - "ml", - "mn-Cyrl", - "mr", - "nb", - "nn", - "or", - "pa", - "pt", - "sl", - "ta", - "te", - "tk", - "und-Ethi", - }; - for (int i = 0; i < availableLanguages.length; i++) { - String languageTag = availableLanguages[i]; - Hyphenator h = loadHyphenator(languageTag); + for (int i = 0; i < AVAILABLE_LANGUAGES.length; i++) { + HyphenationData data = AVAILABLE_LANGUAGES[i]; + Hyphenator h = loadHyphenator(data); if (h != null) { - sMap.put(Locale.forLanguageTag(languageTag), h); + sMap.put(Locale.forLanguageTag(data.mLanguageTag), h); } } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index cb5b073318d8..94c463c8e651 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -1290,7 +1290,8 @@ public class StaticLayout extends Layout { private static native void nFreeBuilder(long nativePtr); private static native void nFinishBuilder(long nativePtr); - /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset); + /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset, + int minPrefix, int minSuffix); private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator); diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java index 910a6b191a01..ca3985449d92 100644 --- a/core/java/android/util/Patterns.java +++ b/core/java/android/util/Patterns.java @@ -243,12 +243,12 @@ public class Patterns { public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; - public static final Pattern IP_ADDRESS - = Pattern.compile( + private static final String IP_ADDRESS_STRING = "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" - + "|[1-9][0-9]|[0-9]))"); + + "|[1-9][0-9]|[0-9]))"; + public static final Pattern IP_ADDRESS = Pattern.compile(IP_ADDRESS_STRING); /** * Valid UCS characters defined in RFC 3987. Excludes space characters. @@ -298,8 +298,8 @@ public class Patterns { private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; - public static final Pattern DOMAIN_NAME - = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); + private static final String DOMAIN_NAME_STR = "(" + HOST_NAME + "|" + IP_ADDRESS_STRING + ")"; + public static final Pattern DOMAIN_NAME = Pattern.compile(DOMAIN_NAME_STR); private static final String PROTOCOL = "(?i:http|https|rtsp)://"; @@ -323,7 +323,7 @@ public class Patterns { public static final Pattern WEB_URL = Pattern.compile("(" + "(" + "(?:" + PROTOCOL + "(?:" + USER_INFO + ")?" + ")?" - + "(?:" + DOMAIN_NAME + ")" + + "(?:" + DOMAIN_NAME_STR + ")" + "(?:" + PORT_NUMBER + ")?" + ")" + "(" + PATH_AND_QUERY + ")?" @@ -346,14 +346,14 @@ public class Patterns { * Regular expression that matches domain names using either {@link #STRICT_HOST_NAME} or * {@link #IP_ADDRESS} */ - private static final Pattern STRICT_DOMAIN_NAME - = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")"); + private static final String STRICT_DOMAIN_NAME = "(?:" + STRICT_HOST_NAME + "|" + + IP_ADDRESS_STRING + ")"; /** * Regular expression that matches domain names without a TLD */ private static final String RELAXED_DOMAIN_NAME = - "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + ")"; + "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS_STRING + ")"; /** * Regular expression to match strings that do not start with a supported protocol. The TLDs diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl index c7340bfa329e..782f3499fb79 100644 --- a/core/java/android/view/IPinnedStackListener.aidl +++ b/core/java/android/view/IPinnedStackListener.aidl @@ -38,9 +38,11 @@ oneway interface IPinnedStackListener { * to be changed (ie. after configuration change, aspect ratio change, etc). It then provides * the components that allow the listener to calculate the movement bounds itself. The * {@param normalBounds} are also the default bounds that the PiP would be entered in its - * current state with the aspect ratio applied. + * current state with the aspect ratio applied. The {@param animatingBounds} are provided + * to indicate the current target bounds of the pinned stack (the final bounds if animating, + * the current bounds if not), which may be helpful in calculating dependent animation bounds. */ - void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, + void onMovementBoundsChanged(in Rect insetBounds, in Rect normalBounds, in Rect animatingBounds, boolean fromImeAdjustement); /** diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 16d46666732d..fe9197867484 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -23,9 +23,7 @@ import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Bundle; import android.util.AttributeSet; -import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; import android.widget.RemoteViews; @@ -67,33 +65,6 @@ public class NotificationHeaderView extends ViewGroup { } } }; - final AccessibilityDelegate mExpandDelegate = new AccessibilityDelegate() { - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (super.performAccessibilityAction(host, action, args)) { - return true; - } - if (action == AccessibilityNodeInfo.ACTION_COLLAPSE - || action == AccessibilityNodeInfo.ACTION_EXPAND) { - mExpandClickListener.onClick(mExpandButton); - return true; - } - return false; - } - - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - // Avoid that the button description is also spoken - info.setClassName(getClass().getName()); - if (mExpanded) { - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); - } else { - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); - } - } - }; private boolean mAcceptAllTouches; public NotificationHeaderView(Context context) { @@ -124,9 +95,6 @@ public class NotificationHeaderView extends ViewGroup { mAppName = findViewById(com.android.internal.R.id.app_name_text); mHeaderText = findViewById(com.android.internal.R.id.header_text); mExpandButton = (ImageView) findViewById(com.android.internal.R.id.expand_button); - if (mExpandButton != null) { - mExpandButton.setAccessibilityDelegate(mExpandDelegate); - } mIcon = (CachingIconView) findViewById(com.android.internal.R.id.icon); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); } @@ -295,13 +263,19 @@ public class NotificationHeaderView extends ViewGroup { private void updateExpandButton() { int drawableId; + int contentDescriptionId; if (mExpanded) { drawableId = com.android.internal.R.drawable.ic_collapse_notification; + contentDescriptionId + = com.android.internal.R.string.expand_button_content_description_expanded; } else { drawableId = com.android.internal.R.drawable.ic_expand_notification; + contentDescriptionId + = com.android.internal.R.string.expand_button_content_description_collapsed; } mExpandButton.setImageDrawable(getContext().getDrawable(drawableId)); mExpandButton.setColorFilter(mOriginalNotificationColor); + mExpandButton.setContentDescription(mContext.getText(contentDescriptionId)); } public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) { @@ -391,7 +365,7 @@ public class NotificationHeaderView extends ViewGroup { break; case MotionEvent.ACTION_UP: if (mTrackGesture) { - mExpandClickListener.onClick(NotificationHeaderView.this); + mExpandButton.performClick(); } break; } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 97a36fd71c0f..536d7e088021 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -201,7 +201,7 @@ final class TextClassifierImpl implements TextClassifier { Preconditions.checkArgument(text != null); Preconditions.checkArgument(startIndex >= 0); Preconditions.checkArgument(endIndex <= text.length()); - Preconditions.checkArgument(endIndex >= startIndex); + Preconditions.checkArgument(endIndex > startIndex); } /** diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 679053299666..b75193536f32 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -60,14 +60,14 @@ final class SelectionActionModeHelper { mEditor = Preconditions.checkNotNull(editor); final TextView textView = mEditor.getTextView(); mTextClassificationHelper = new TextClassificationHelper( - textView.getTextClassifier(), textView.getText(), - textView.getSelectionStart(), textView.getSelectionEnd()); + textView.getTextClassifier(), textView.getText(), 0, 1); } public void startActionModeAsync() { cancelAsyncTask(); - if (isNoOpTextClassifier()) { + if (isNoOpTextClassifier() || !hasSelection()) { // No need to make an async call for a no-op TextClassifier. + // Do not call the TextClassifier if there is no selection. startActionMode(null); } else { resetTextClassificationHelper(); @@ -84,8 +84,9 @@ final class SelectionActionModeHelper { public void invalidateActionModeAsync() { cancelAsyncTask(); - if (isNoOpTextClassifier()) { + if (isNoOpTextClassifier() || !hasSelection()) { // No need to make an async call for a no-op TextClassifier. + // Do not call the TextClassifier if there is no selection. invalidateActionMode(null); } else { resetTextClassificationHelper(); @@ -126,6 +127,11 @@ final class SelectionActionModeHelper { return mEditor.getTextView().getTextClassifier() == TextClassifier.NO_OP; } + private boolean hasSelection() { + final TextView textView = mEditor.getTextView(); + return textView.getSelectionEnd() > textView.getSelectionStart(); + } + private void startActionMode(@Nullable SelectionResult result) { final TextView textView = mEditor.getTextView(); final CharSequence text = textView.getText(); @@ -311,6 +317,7 @@ final class SelectionActionModeHelper { CharSequence text, int selectionStart, int selectionEnd) { mTextClassifier = Preconditions.checkNotNull(textClassifier); mText = Preconditions.checkNotNull(text).toString(); + Preconditions.checkArgument(selectionEnd > selectionStart); mSelectionStart = selectionStart; mSelectionEnd = selectionEnd; } diff --git a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java index cf1bf62cdeb2..bab7cb352563 100644 --- a/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java +++ b/core/java/com/android/internal/hardware/AmbientDisplayConfiguration.java @@ -19,6 +19,7 @@ package com.android.internal.hardware; import com.android.internal.R; import android.content.Context; +import android.os.Build; import android.provider.Settings; import android.text.TextUtils; @@ -33,7 +34,8 @@ public class AmbientDisplayConfiguration { public boolean enabled(int user) { return pulseOnNotificationEnabled(user) || pulseOnPickupEnabled(user) - || pulseOnDoubleTapEnabled(user); + || pulseOnDoubleTapEnabled(user) + || alwaysOnEnabled(user); } public boolean available() { @@ -72,6 +74,16 @@ public class AmbientDisplayConfiguration { return mContext.getResources().getString(R.string.config_dozeDoubleTapSensorType); } + public boolean alwaysOnEnabled(int user) { + return boolSetting(Settings.Secure.DOZE_ALWAYS_ON, user) + && alwaysOnAvailable(); + } + + public boolean alwaysOnAvailable() { + // TODO: introduce config_dozeAlwaysOnAvailable. For now just debuggable builds. + return Build.IS_DEBUGGABLE && ambientDisplayAvailable(); + } + public String ambientDisplayComponent() { return mContext.getResources().getString(R.string.config_dozeComponent); } diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index c8bf302e46d5..949e7ac50a7b 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -29,8 +29,10 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; */ public class MetricsLogger { // define metric categories in frameworks/base/proto/src/metrics_constants.proto. + // mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN; + public static final int LOGTAG = EventLogTags.SYSUI_MULTI_ACTION; public static void visible(Context context, int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { @@ -125,6 +127,7 @@ public class MetricsLogger { /** Increment the bucket with the integer label on the histogram with the given name. */ public static void histogram(Context context, String name, int bucket) { + // see LogHistogram in system/core/libmetricslogger/metrics_logger.cpp EventLogTags.writeSysuiHistogram(name, bucket); EventLogTags.writeSysuiMultiAction( new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) diff --git a/core/java/com/android/internal/logging/legacy/EventLogCollector.java b/core/java/com/android/internal/logging/legacy/EventLogCollector.java deleted file mode 100644 index 598f0d51c1b9..000000000000 --- a/core/java/com/android/internal/logging/legacy/EventLogCollector.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2017 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.logging.legacy; - -import android.util.ArrayMap; -import android.util.EventLog; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; - -/** - * Scan the event log for interaction metrics events. - * @hide - */ -public class EventLogCollector { - private static final String TAG = "EventLogCollector"; - - // TODO replace this with GoogleLogTags.TRON_HEARTBEAT - @VisibleForTesting - static final int TRON_HEARTBEAT = 208000; - - private static EventLogCollector sInstance; - - private final ArrayMap<Integer, TagParser> mTagParsers; - private int[] mInterestingTags; - - private LogReader mLogReader; - - private EventLogCollector() { - mTagParsers = new ArrayMap<>(); - addParser(new PowerScreenStateParser()); - addParser(new SysuiMultiActionParser()); - - mLogReader = new LogReader(); - } - - public static EventLogCollector getInstance() { - if (sInstance == null) { - sInstance = new EventLogCollector(); - } - return sInstance; - } - - @VisibleForTesting - public void setLogReader(LogReader logReader) { - mLogReader = logReader; - } - - private int[] getInterestingTags() { - if (mInterestingTags == null) { - mInterestingTags = new int[mTagParsers.size()]; - for (int i = 0; i < mTagParsers.size(); i++) { - mInterestingTags[i] = mTagParsers.valueAt(i).getTag(); - } - } - return mInterestingTags; - } - - // I would customize ArrayMap to add put(TagParser), but ArrayMap is final. - @VisibleForTesting - void addParser(TagParser parser) { - mTagParsers.put(parser.getTag(), parser); - mInterestingTags = null; - } - - public void collect(LegacyConversionLogger logger) { - collect(logger, 0L); - } - - public long collect(TronLogger logger, long lastSeenEventMs) { - long lastEventMs = 0L; - final boolean debug = Util.debug(); - - if (debug) { - Log.d(TAG, "Eventlog Collection"); - } - ArrayList<Event> events = new ArrayList<>(); - try { - mLogReader.readEvents(getInterestingTags(), events); - } catch (IOException e) { - e.printStackTrace(); - } - if (debug) { - Log.d(TAG, "read this many events: " + events.size()); - } - - for (Event event : events) { - final long millis = event.getTimeNanos() / 1000000; - if (millis > lastSeenEventMs) { - final int tag = event.getTag(); - TagParser parser = mTagParsers.get(tag); - if (parser == null) { - if (debug) { - Log.d(TAG, "unknown tag: " + tag); - } - continue; - } - if (debug) { - Log.d(TAG, "parsing tag: " + tag); - } - parser.parseEvent(logger, event); - lastEventMs = Math.max(lastEventMs, millis); - } else { - if (debug) { - Log.d(TAG, "old event: " + millis + " < " + lastSeenEventMs); - } - } - } - return lastEventMs; - } - - @VisibleForTesting - static class Event { - long mTimeNanos; - int mTag; - Object mData; - - Event(long timeNanos, int tag, Object data) { - super(); - mTimeNanos = timeNanos; - mTag = tag; - mData = data; - } - - Event(EventLog.Event event) { - mTimeNanos = event.getTimeNanos(); - mTag = event.getTag(); - mData = event.getData(); - } - - public long getTimeNanos() { - return mTimeNanos; - } - - public int getTag() { - return mTag; - } - - public Object getData() { - return mData; - } - } - - @VisibleForTesting - static class LogReader { - public void readEvents(int[] tags, Collection<Event> events) throws IOException { - // Testing in Android: the Static Final Class Strikes Back! - ArrayList<EventLog.Event> nativeEvents = new ArrayList<>(); - EventLog.readEventsOnWrapping(tags, 0L, nativeEvents); - for (EventLog.Event nativeEvent : nativeEvents) { - Event event = new Event(nativeEvent); - events.add(event); - } - } - } -} diff --git a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java b/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java deleted file mode 100644 index 1209e4d8dc8c..000000000000 --- a/core/java/com/android/internal/logging/legacy/LegacyConversionLogger.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2017 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.logging.legacy; - -import android.metrics.LogMaker; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Queue; - -/** @hide */ -public class LegacyConversionLogger implements TronLogger { - private final Queue<LogMaker> mQueue; - private HashMap<String, Boolean> mConfig; - - public LegacyConversionLogger() { - mQueue = new LinkedList<>(); - } - - public Queue<LogMaker> getEvents() { - return mQueue; - } - - @Override - public void increment(String counterName) { - LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) - .setCounterName(counterName) - .setCounterValue(1); - mQueue.add(b); - } - - @Override - public void incrementBy(String counterName, int value) { - LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_COUNTER) - .setCounterName(counterName) - .setCounterValue(value); - mQueue.add(b); - } - - @Override - public void incrementIntHistogram(String counterName, int bucket) { - LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) - .setCounterName(counterName) - .setCounterBucket(bucket) - .setCounterValue(1); - mQueue.add(b); - } - - @Override - public void incrementLongHistogram(String counterName, long bucket) { - LogMaker b = new LogMaker(MetricsEvent.RESERVED_FOR_LOGBUILDER_HISTOGRAM) - .setCounterName(counterName) - .setCounterBucket(bucket) - .setCounterValue(1); - mQueue.add(b); - } - - @Override - public LogMaker obtain() { - return new LogMaker(MetricsEvent.VIEW_UNKNOWN); - } - - @Override - public void dispose(LogMaker proto) { - } - - @Override - public void addEvent(LogMaker proto) { - mQueue.add(proto); - } - - @Override - public boolean getConfig(String configName) { - if (mConfig != null && mConfig.containsKey(configName)) { - return mConfig.get(configName); - } - return false; - } - - @Override - public void setConfig(String configName, boolean newValue) { - if (mConfig == null) { - mConfig = new HashMap<>(); - } - mConfig.put(configName, newValue); - } -} diff --git a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java b/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java deleted file mode 100644 index e9baf9baf8da..000000000000 --- a/core/java/com/android/internal/logging/legacy/PowerScreenStateParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2017 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.logging.legacy; - -import android.util.Log; - -import android.metrics.LogMaker; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -/** - * Parse the Android lockscreen gesture logs. - * @hide - */ -public class PowerScreenStateParser extends TagParser { - private static final String TAG = "PowerScreenStateParser"; - private static final int EVENTLOG_TAG = 2728; - - // source of truth is android.view.WindowManagerPolicy, why: - // 0: on - // 1: OFF_BECAUSE_OF_ADMIN - // 2: OFF_BECAUSE_OF_USER - // 3: OFF_BECAUSE_OF_TIMEOUT - - @Override - public int getTag() { - return EVENTLOG_TAG; - } - - @Override - public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) { - final boolean debug = Util.debug(); - if (operands.length >= 2) { - try { - // (offOrOn|1|5),(becauseOfUser|1|5),(totalTouchDownTime|2|3),(touchCycles|1|1) - boolean state = (((Integer) operands[0]).intValue()) == 1; - int why = ((Integer) operands[1]).intValue(); - - LogMaker proto = logger.obtain(); - proto.setCategory(MetricsEvent.SCREEN); - proto.setType(state ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE); - proto.setTimestamp(eventTimeMs); - proto.setSubtype(why); - logger.addEvent(proto); - } catch (ClassCastException e) { - if (debug) { - Log.e(TAG, "unexpected operand type: ", e); - } - } - } else if (debug) { - Log.w(TAG, "wrong number of operands: " + operands.length); - } - } -} diff --git a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java b/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java deleted file mode 100644 index 0c77b7a6ccd0..000000000000 --- a/core/java/com/android/internal/logging/legacy/SysuiMultiActionParser.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2017 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.logging.legacy; - -import android.util.Log; - -import android.metrics.LogMaker; - -/** - * ...and one parser to rule them all. - * - * This should, at some point in the future, be the only parser. - * @hide - */ -public class SysuiMultiActionParser extends TagParser { - private static final String TAG = "SysuiMultiActionParser"; - private static final int EVENTLOG_TAG = 524292; - - @Override - public int getTag() { - return EVENTLOG_TAG; - } - - @Override - public void parseEvent(TronLogger logger, long eventTimeMs, Object[] operands) { - final boolean debug = Util.debug(); - try { - logger.addEvent(new LogMaker(operands).setTimestamp(eventTimeMs)); - } catch (ClassCastException e) { - if (debug) { - Log.e(TAG, "unexpected operand type: ", e); - } - } - } -} diff --git a/core/java/com/android/internal/logging/legacy/TagParser.java b/core/java/com/android/internal/logging/legacy/TagParser.java deleted file mode 100755 index 3bffdd522236..000000000000 --- a/core/java/com/android/internal/logging/legacy/TagParser.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2017 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.logging.legacy; - -import android.util.Log; - -import android.metrics.LogMaker; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; - -/** - * Abstraction layer between EventLog static classes and the actual TagParsers. - * @hide - */ -public abstract class TagParser { - private static final String TAG = "TagParser"; - - protected int mSinceCreationMillis; - protected int mSinceUpdateMillis; - protected int mSinceVisibleMillis; - - abstract int getTag(); - - @VisibleForTesting - abstract public void parseEvent(TronLogger logger, long eventTimeMs, Object[] objects); - - /** - * Parse the event into the proto: return true if proto was modified. - */ - public void parseEvent(TronLogger logger, EventLogCollector.Event event) { - final boolean debug = Util.debug(); - Object data = event.getData(); - Object[] objects; - if (data instanceof Object[]) { - objects = (Object[]) data; - for (int i = 0; i < objects.length; i++) { - if (objects[i] == null) { - if (debug) { - Log.d(TAG, "unexpected null value:" + event.getTag()); - } - return; - } - } - } else { - // wrap scalar objects - objects = new Object[1]; - objects[0] = data; - } - - parseEvent(logger, event.getTimeNanos() / 1000000, objects); - } - - protected void resetTimes() { - mSinceCreationMillis = 0; - mSinceUpdateMillis = 0; - mSinceVisibleMillis = 0; - } - - public void parseTimes(Object[] operands, int index) { - resetTimes(); - - if (operands.length > index && operands[index] instanceof Integer) { - mSinceCreationMillis = (Integer) operands[index]; - } - - index++; - if (operands.length > index && operands[index] instanceof Integer) { - mSinceUpdateMillis = (Integer) operands[index]; - } - - index++; - if (operands.length > index && operands[index] instanceof Integer) { - mSinceVisibleMillis = (Integer) operands[index]; - } - } - - public void filltimes(LogMaker proto) { - if (mSinceCreationMillis != 0) { - proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, - mSinceCreationMillis); - } - if (mSinceUpdateMillis != 0) { - proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, - mSinceUpdateMillis); - } - if (mSinceVisibleMillis != 0) { - proto.addTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, - mSinceVisibleMillis); - } - } -} diff --git a/core/java/com/android/internal/logging/legacy/TronCounters.java b/core/java/com/android/internal/logging/legacy/TronCounters.java deleted file mode 100644 index e0828a20d6ad..000000000000 --- a/core/java/com/android/internal/logging/legacy/TronCounters.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2017 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.logging.legacy; - -/** - * Names of the counters that the Tron package defines. - * - * Other counter names may be generated by AOSP code. - * @hide - */ -public class TronCounters { - - static final String TRON_COLLECTIONS = "tron_collections"; - - static final String TRON_COLLECTION_PERIOD = "tron_collection_period_minutes"; - - static final String TRON_EVENTLOG_LENGTH = "tron_eventlog_horizon"; - - static final String TRON_NOTE_DISMISS = "tron_note_dismiss"; - - static final String TRON_NOTE_DISMISS_BY_USER = "tron_note_dismiss_user"; - - static final String TRON_NOTE_DISMISS_BY_CLICK = "tron_note_dismiss_click"; - - static final String TRON_NOTE_DISMISS_BY_LISTENER = "tron_note_dismiss_listener"; - - static final String TRON_NOTE_DISMISS_BY_BAN = "tron_note_dismiss_ban"; - - static final String TRON_NOTE_REVEALED = "tron_note_revealed"; - - static final String TRON_NOTIFICATION_LOAD = "tron_notification_load"; - - static final String TRON_VIEW = "tron_view"; - - static final String TRON_ACTION = "tron_action"; - - static final String TRON_DETAIL = "tron_detail"; - - static final String TRON_NOTE_LIFETIME = "tron_note_lifetime"; - - /** Append the AOSP-generated name */ - static final String TRON_AOSP_PREFIX = "tron_varz_"; - - static final String TRON_ACTION_BAD_INT = "tron_action_bad_int"; - - static final String TRON_NOTE_FRESHNESS = "tron_note_freshness"; - - static final String TRON_NOTE_BUZZ = "tron_note_buzz"; - - static final String TRON_NOTE_BEEP = "tron_note_beep"; - - static final String TRON_NOTE_BLINK = "tron_note_blink"; - - static final String TRON_DISABLE = "tron_disable"; - - static final String TRON_LAST_HEART_AGE = "tron_last_heart_minutes"; - - static final String TRON_HEARTS_SEEN = "tron_hearts_seen"; -} diff --git a/core/java/com/android/internal/logging/legacy/TronLogger.java b/core/java/com/android/internal/logging/legacy/TronLogger.java deleted file mode 100644 index ee9341ab2138..000000000000 --- a/core/java/com/android/internal/logging/legacy/TronLogger.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2017 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.logging.legacy; - -import android.metrics.LogMaker; - -/** - * An entity that knows how to log events and counters. - */ -public interface TronLogger { - /** Add one to the named counter. */ - void increment(String counterName); - - /** Add an arbitrary value to the named counter. */ - void incrementBy(String counterName, int value); - - /** Increment a specified bucket on the named histogram by one. */ - void incrementIntHistogram(String counterName, int bucket); - - /** Increment the specified bucket on the named histogram by one. */ - void incrementLongHistogram(String counterName, long bucket); - - /** Obtain a SystemUiEvent proto, must release this with dispose() or addEvent(). */ - LogMaker obtain(); - - void dispose(LogMaker proto); - - /** Submit an event to be logged. Logger will dispose of proto. */ - void addEvent(LogMaker proto); - - /** Get a config flag. */ - boolean getConfig(String configName); - - /** Set a config flag. */ - void setConfig(String configName, boolean newValue); -} diff --git a/core/java/com/android/internal/logging/legacy/Util.java b/core/java/com/android/internal/logging/legacy/Util.java deleted file mode 100644 index 99f71caf5d29..000000000000 --- a/core/java/com/android/internal/logging/legacy/Util.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2017 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.logging.legacy; - -/** - * Created by cwren on 11/21/16. - */ -public class Util { - public static boolean debug() { - return false; - } -} diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java index 040f150e8c95..f88ac495fe52 100644 --- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java @@ -18,9 +18,11 @@ package com.android.internal.policy; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.util.Size; import android.view.Gravity; import android.view.ViewConfiguration; import android.widget.Scroller; @@ -56,6 +58,10 @@ public class PipSnapAlgorithm { private final int mDefaultSnapMode = SNAP_MODE_CORNERS_AND_EDGES; private int mSnapMode = mDefaultSnapMode; + private final float mDefaultSizePercent; + private final float mMinAspectRatioForMinSize; + private final float mMaxAspectRatioForMinSize; + private Scroller mScroller; private int mOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -63,9 +69,15 @@ public class PipSnapAlgorithm { private boolean mIsMinimized; public PipSnapAlgorithm(Context context) { + Resources res = context.getResources(); mContext = context; - mMinimizedVisibleSize = context.getResources().getDimensionPixelSize( + mMinimizedVisibleSize = res.getDimensionPixelSize( com.android.internal.R.dimen.pip_minimized_visible_size); + mDefaultSizePercent = res.getFloat( + com.android.internal.R.dimen.config_pictureInPictureDefaultSizePercent); + mMaxAspectRatioForMinSize = res.getFloat( + com.android.internal.R.dimen.config_pictureInPictureAspectRatioLimitForMinSize); + mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize; onConfigurationChanged(); } @@ -242,6 +254,40 @@ public class PipSnapAlgorithm { } /** + * @return the size of the PiP at the given {@param aspectRatio}, ensuring that the minimum edge + * is at least {@param minEdgeSize}. + */ + public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth, + int displayHeight) { + final int smallestDisplaySize = Math.min(displayWidth, displayHeight); + final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent); + + final int width; + final int height; + if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) { + // Beyond these points, we can just use the min size as the shorter edge + if (aspectRatio <= 1) { + // Portrait, width is the minimum size + width = minSize; + height = Math.round(width / aspectRatio); + } else { + // Landscape, height is the minimum size + height = minSize; + width = Math.round(height * aspectRatio); + } + } else { + // Within these points, we ensure that the bounds fit within the radius of the limits + // at the points + final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize; + final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize); + height = (int) Math.round(Math.sqrt((radius * radius) / + (aspectRatio * aspectRatio + 1))); + width = Math.round(height * aspectRatio); + } + return new Size(width, height); + } + + /** * @return the closest point in {@param points} to the given {@param x} and {@param y}. */ private Point findClosestPoint(int x, int y, Point[] points) { diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java index f4f49b1e4ffe..c64ace403f76 100644 --- a/core/java/com/android/internal/widget/NotificationExpandButton.java +++ b/core/java/com/android/internal/widget/NotificationExpandButton.java @@ -20,6 +20,8 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.Button; import android.widget.ImageView; import android.widget.RemoteViews; @@ -59,4 +61,10 @@ public class NotificationExpandButton extends ImageView { rect.top = rect.centerY() - touchTargetSize / 2; rect.bottom = rect.top + touchTargetSize; } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(Button.class.getName()); + } } diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index 90ed6eb5e60e..4a445d8705ca 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -121,7 +121,8 @@ static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) { b->finish(); } -static jlong nLoadHyphenator(JNIEnv* env, jclass, jobject buffer, jint offset) { +static jlong nLoadHyphenator(JNIEnv* env, jclass, jobject buffer, jint offset, + jint minPrefix, jint minSuffix) { const uint8_t* bytebuf = nullptr; if (buffer != nullptr) { void* rawbuf = env->GetDirectBufferAddress(buffer); @@ -131,7 +132,8 @@ static jlong nLoadHyphenator(JNIEnv* env, jclass, jobject buffer, jint offset) { ALOGE("failed to get direct buffer address"); } } - minikin::Hyphenator* hyphenator = minikin::Hyphenator::loadBinary(bytebuf); + minikin::Hyphenator* hyphenator = minikin::Hyphenator::loadBinary( + bytebuf, minPrefix, minSuffix); return reinterpret_cast<jlong>(hyphenator); } @@ -191,7 +193,7 @@ static const JNINativeMethod gMethods[] = { {"nNewBuilder", "()J", (void*) nNewBuilder}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, {"nFinishBuilder", "(J)V", (void*) nFinishBuilder}, - {"nLoadHyphenator", "(Ljava/nio/ByteBuffer;I)J", (void*) nLoadHyphenator}, + {"nLoadHyphenator", "(Ljava/nio/ByteBuffer;III)J", (void*) nLoadHyphenator}, {"nSetLocale", "(JLjava/lang/String;J)V", (void*) nSetLocale}, {"nSetupParagraph", "(J[CIFIF[IIIIZ)V", (void*) nSetupParagraph}, {"nSetIndents", "(J[I)V", (void*) nSetIndents}, diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 0dfeb6286fb3..448cd2e30ef4 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -97,7 +97,7 @@ android:layout_height="wrap_content" android:paddingTop="1dp" android:visibility="gone" - android:contentDescription="@string/expand_button_content_description" + android:contentDescription="@string/expand_button_content_description_collapsed" /> <ImageView android:id="@+id/profile_badge" android:layout_width="@dimen/notification_badge_size" diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml index c27cb06deb8d..1987ac454d86 100644 --- a/core/res/res/values-television/config.xml +++ b/core/res/res/values-television/config.xml @@ -24,9 +24,10 @@ <!-- Flags enabling default window features. See Window.java --> <bool name="config_defaultWindowFeatureOptionsPanel">false</bool> - <!-- Max default size [WIDTHxHEIGHT] on screen for picture-in-picture windows to fit inside. - These values are in DPs and will be converted to pixel sizes internally. --> - <string translatable="false" name="config_defaultPictureInPictureSize">240x135</string> + <!-- The percentage of the screen width to use for the default width or height of + picture-in-picture windows. Regardless of the percent set here, calculated size will never + be smaller than @dimen/default_minimal_size_pip_resizable_task. --> + <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.14</item> <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows. These values are in DPs and will be converted to pixel sizes internally. --> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 2e09e7d1b0c5..f8d5241b8467 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3336,7 +3336,7 @@ {@link android.accessibilityservice.AccessibilityService#SERVICE_META_DATA} meta-data entry. --> <declare-styleable name="AccessibilityService"> - <!-- The event types this serivce would like to receive as specified in + <!-- The event types this service would like to receive as specified in {@link android.view.accessibility.AccessibilityEvent}. This setting can be changed at runtime by calling {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) @@ -3395,11 +3395,11 @@ <!-- Receives {@link android.view.accessibility.AccessibilityEvent#TYPES_ALL_MASK} i.e. all events. --> <flag name="typeAllMask" value="0xffffffff" /> </attr> - <!-- Comma separated package names from which this serivce would like to receive events (leave out for all packages). + <!-- Comma separated package names from which this service would like to receive events (leave out for all packages). {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> <attr name="packageNames" format="string" /> - <!-- The feedback types this serivce provides as specified in + <!-- The feedback types this service provides as specified in {@link android.accessibilityservice.AccessibilityServiceInfo}. This setting can be changed at runtime by calling {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) @@ -3419,7 +3419,7 @@ <flag name="feedbackAllMask" value="0xffffffff" /> </attr> <!-- The minimal period in milliseconds between two accessibility events of the same type - are sent to this serivce. This setting can be changed at runtime by calling + are sent to this service. This setting can be changed at runtime by calling {@link android.accessibilityservice.AccessibilityService#setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) android.accessibilityservice.AccessibilityService.setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)}. --> <attr name="notificationTimeout" format="integer" /> @@ -3498,6 +3498,8 @@ <attr name="canCaptureFingerprintGestures" format="boolean" /> <!-- Short description of the accessibility service purpose or behavior.--> <attr name="description" /> + <!-- Brief summary of the accessibility service purpose or behavior. --> + <attr name="summary" /> </declare-styleable> <!-- Use <code>print-service</code> as the root tag of the XML resource that @@ -8565,6 +8567,7 @@ <!-- Attributes that are read when parsing a <fontfamily> tag. --> <declare-styleable name="FontFamily"> <attr name="fontProviderAuthority" format="string" /> + <attr name="fontProviderPackage" format="string" /> <attr name="fontProviderQuery" format="string" /> </declare-styleable> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ab2e090b2869..a3a0c830a0cf 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2545,9 +2545,17 @@ These values are in DPs and will be converted to pixel sizes internally. --> <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string> - <!-- Max default size [WIDTHxHEIGHT] on screen for picture-in-picture windows to fit inside. - These values are in DPs and will be converted to pixel sizes internally. --> - <string translatable="false" name="config_defaultPictureInPictureSize">192x120</string> + <!-- The percentage of the screen width to use for the default width or height of + picture-in-picture windows. Regardless of the percent set here, calculated size will never + be smaller than @dimen/default_minimal_size_pip_resizable_task. --> + <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.23</item> + + <!-- The default aspect ratio for picture-in-picture windows. --> + <item name="config_pictureInPictureDefaultAspectRatio" format="float" type="dimen">1.777778</item> + + <!-- This is the limit for the max and min aspect ratio (1 / this value) at which the min size + will be used instead of an adaptive size based loosely on area. --> + <item name="config_pictureInPictureAspectRatioLimitForMinSize" format="float" type="dimen">1.777778</item> <!-- The default gravity for the picture-in-picture window. Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 0ab17842ebb7..d0127a3a6cd6 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -486,6 +486,9 @@ <!-- The default minimal size of a resizable task, in both dimensions. --> <dimen name="default_minimal_size_resizable_task">220dp</dimen> + <!-- The default minimal size of a PiP task, in both dimensions. --> + <dimen name="default_minimal_size_pip_resizable_task">108dp</dimen> + <!-- Height of a task when in minimized mode from the top when launcher is resizable. --> <dimen name="task_height_of_minimized_mode">80dp</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index df3962c5dd5c..0d90cd2f9b44 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2802,6 +2802,7 @@ <public name="requiredFeature" /> <public name="requiredNotFeature" /> <public name="autoFillHint" /> + <public name="fontProviderPackage" /> </public-group> <public-group type="style" first-id="0x010302e0"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index ea0666413f88..bd8d5724ac50 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4382,8 +4382,11 @@ <!-- Content description of the work profile icon in the notification. --> <string name="notification_work_profile_content_description">Work profile</string> - <!-- Content description of the expand button icon in the notification.--> - <string name="expand_button_content_description">Expand button</string> + <!-- Content description of the expand button icon in the notification when collaped.--> + <string name="expand_button_content_description_collapsed">Expand</string> + + <!-- Content description of the expand button icon in the notification when expanded.--> + <string name="expand_button_content_description_expanded">Collapse</string> <!-- Accessibility action description on the expand button. --> <string name="expand_action_accessibility">toggle expansion</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 3ba6a274ee8a..32babab658bb 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -318,7 +318,9 @@ <java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" /> <java-symbol type="bool" name="config_enableAppWidgetService" /> <java-symbol type="string" name="config_defaultPictureInPictureScreenEdgeInsets" /> - <java-symbol type="string" name="config_defaultPictureInPictureSize" /> + <java-symbol type="dimen" name="config_pictureInPictureDefaultSizePercent" /> + <java-symbol type="dimen" name="config_pictureInPictureDefaultAspectRatio" /> + <java-symbol type="dimen" name="config_pictureInPictureAspectRatioLimitForMinSize" /> <java-symbol type="integer" name="config_defaultPictureInPictureGravity" /> <java-symbol type="dimen" name="config_pictureInPictureMinAspectRatio" /> <java-symbol type="dimen" name="config_pictureInPictureMaxAspectRatio" /> @@ -1777,6 +1779,7 @@ <java-symbol type="id" name="replace_message" /> <java-symbol type="fraction" name="config_dimBehindFadeDuration" /> <java-symbol type="dimen" name="default_minimal_size_resizable_task" /> + <java-symbol type="dimen" name="default_minimal_size_pip_resizable_task" /> <java-symbol type="dimen" name="task_height_of_minimized_mode" /> <java-symbol type="fraction" name="config_screenAutoBrightnessDozeScaleFactor" /> <java-symbol type="fraction" name="config_autoBrightnessAdjustmentMaxGamma" /> @@ -2885,6 +2888,9 @@ <java-symbol type="style" name="Widget.LockPatternView" /> <java-symbol type="attr" name="lockPatternStyle" /> + <java-symbol type="string" name="expand_button_content_description_collapsed" /> + <java-symbol type="string" name="expand_button_content_description_expanded" /> + <!-- Colon separated list of package names that should be granted Notification Listener access --> <java-symbol type="string" name="config_defaultListenerAccessPackages" /> diff --git a/core/tests/coretests/res/font/samplexmldownloadedfont.xml b/core/tests/coretests/res/font/samplexmldownloadedfont.xml index 35391bd598b3..f1bdc473b9d2 100644 --- a/core/tests/coretests/res/font/samplexmldownloadedfont.xml +++ b/core/tests/coretests/res/font/samplexmldownloadedfont.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <font-family xmlns:android="http://schemas.android.com/apk/res/android" - android:fontProviderAuthority="com.example.test.fontprovider" + android:fontProviderAuthority="com.example.test.fontprovider.authority" + android:fontProviderPackage="com.example.test.fontprovider.package" android:fontProviderQuery="MyRequestedFont"> </font-family>
\ No newline at end of file diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java index 8b536a7a90b5..23d3aa5a64ab 100644 --- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java +++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java @@ -91,7 +91,8 @@ public class FontResourcesParserTest { List<FontConfig.Family> families = result.getFamilies(); assertEquals(1, families.size()); FontConfig.Family family = families.get(0); - assertEquals("com.example.test.fontprovider", family.getProviderAuthority()); + assertEquals("com.example.test.fontprovider.authority", family.getProviderAuthority()); + assertEquals("com.example.test.fontprovider.package", family.getProviderPackage()); assertEquals("MyRequestedFont", family.getQuery()); assertNull(family.getFonts()); } diff --git a/core/tests/coretests/src/android/metrics/LogMakerTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java index b9c973fb09cd..ece44bebf9ba 100644 --- a/core/tests/coretests/src/android/metrics/LogMakerTest.java +++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java @@ -122,6 +122,55 @@ public class LogMakerTest extends TestCase { assertEquals(null, builder.getTaggedData(1)); } + public void testClearFieldLeavesOtherFieldsIntact() { + LogMaker builder = new LogMaker(0); + builder.setPackageName("package.name"); + builder.setSubtype(10); + builder.clearPackageName(); + assertEquals(null, builder.getPackageName()); + assertEquals(10, builder.getSubtype()); + } + + public void testSetAndClearCategory() { + LogMaker builder = new LogMaker(0); + builder.setCategory(MetricsEvent.MAIN_SETTINGS); + assertEquals(MetricsEvent.MAIN_SETTINGS, builder.getCategory()); + builder.clearCategory(); + assertEquals(MetricsEvent.VIEW_UNKNOWN, builder.getCategory()); + } + + public void testSetAndClearType() { + LogMaker builder = new LogMaker(0); + builder.setType(MetricsEvent.TYPE_OPEN); + assertEquals(MetricsEvent.TYPE_OPEN, builder.getType()); + builder.clearType(); + assertEquals(MetricsEvent.TYPE_UNKNOWN, builder.getType()); + } + + public void testSetAndClearSubtype() { + LogMaker builder = new LogMaker(0); + builder.setSubtype(1); + assertEquals(1, builder.getSubtype()); + builder.clearSubtype(); + assertEquals(0, builder.getSubtype()); + } + + public void testSetAndClearTimestamp() { + LogMaker builder = new LogMaker(0); + builder.setTimestamp(1); + assertEquals(1, builder.getTimestamp()); + builder.clearTimestamp(); + assertEquals(0, builder.getTimestamp()); + } + + public void testSetAndClearPackageName() { + LogMaker builder = new LogMaker(0); + builder.setPackageName("package.name"); + assertEquals("package.name", builder.getPackageName()); + builder.clearPackageName(); + assertEquals(null, builder.getPackageName()); + } + public void testGiantLogOmitted() { LogMaker badBuilder = new LogMaker(0); StringBuilder b = new StringBuilder(); diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java new file mode 100644 index 000000000000..db623a4a2044 --- /dev/null +++ b/core/tests/coretests/src/android/provider/FontsContractTest.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2017 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.provider; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.Signature; +import android.graphics.Typeface; +import android.graphics.fonts.FontRequest; +import android.graphics.fonts.FontResult; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.support.test.filters.SmallTest; +import android.test.ProviderTestCase2; +import android.util.Base64; + +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link FontsContract}. + */ +@SmallTest +public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> { + private static final byte[] BYTE_ARRAY = + Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT); + private static final String PACKAGE_NAME = "com.my.font.provider.package"; + + private final FontRequest request = new FontRequest( + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query"); + private TestFontsProvider mProvider; + private FontsContract mContract; + private ResultReceiver mResultReceiver; + private PackageManager mPackageManager; + + public FontsContractTest() { + super(TestFontsProvider.class, TestFontsProvider.AUTHORITY); + } + + public void setUp() throws Exception { + super.setUp(); + + mProvider = getProvider(); + mPackageManager = mock(PackageManager.class); + mContract = new FontsContract(getMockContext(), mPackageManager); + mResultReceiver = mock(ResultReceiver.class); + } + + public void testGetFontFromProvider() { + mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mResultReceiver).send(eq(FontsContract.RESULT_CODE_OK), bundleCaptor.capture()); + + Bundle bundle = bundleCaptor.getValue(); + assertNotNull(bundle); + List<FontResult> resultList = + bundle.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS); + assertNotNull(resultList); + assertEquals(1, resultList.size()); + FontResult fontResult = resultList.get(0); + assertEquals(TestFontsProvider.TTC_INDEX, fontResult.getTtcIndex()); + assertEquals(TestFontsProvider.VARIATION_SETTINGS, fontResult.getFontVariationSettings()); + assertEquals(TestFontsProvider.STYLE, fontResult.getStyle()); + assertNotNull(fontResult.getFileDescriptor()); + } + + public void testGetFontFromProvider_providerDoesntReturnAllFields() { + mProvider.setReturnAllFields(false); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY); + verify(mResultReceiver).send(eq(FontsContract.RESULT_CODE_OK), bundleCaptor.capture()); + + Bundle bundle = bundleCaptor.getValue(); + assertNotNull(bundle); + List<FontResult> resultList = + bundle.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS); + assertNotNull(resultList); + assertEquals(1, resultList.size()); + FontResult fontResult = resultList.get(0); + assertEquals(0, fontResult.getTtcIndex()); + assertNull(fontResult.getFontVariationSettings()); + assertEquals(Typeface.NORMAL, fontResult.getStyle()); + assertNotNull(fontResult.getFileDescriptor()); + } + + public void testGetProvider_providerNotFound() { + when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(null); + + ProviderInfo result = mContract.getProvider(request); + + assertNull(result); + } + + public void testGetProvider_providerIsSystemApp() throws PackageManager.NameNotFoundException { + ProviderInfo info = setupPackageManager(); + info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info); + + ProviderInfo result = mContract.getProvider(request); + + assertEquals(info, result); + } + + public void testGetProvider_providerIsSystemAppWrongPackage() + throws PackageManager.NameNotFoundException { + ProviderInfo info = setupPackageManager(); + info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; + when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info); + + ProviderInfo result = mContract.getProvider( + new FontRequest(TestFontsProvider.AUTHORITY, "com.wrong.package", "query")); + + assertNull(result); + } + + public void testGetProvider_providerIsNonSystemAppNoCerts() + throws PackageManager.NameNotFoundException { + setupPackageManager(); + + // The default request is missing the certificates info. + ProviderInfo result = mContract.getProvider(request); + + assertNull(result); + } + + public void testGetProvider_providerIsNonSystemAppWrongCerts() + throws PackageManager.NameNotFoundException { + setupPackageManager(); + + byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); + List<byte[]> certList = Arrays.asList(wrongCert); + FontRequest requestWrongCerts = new FontRequest( + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList)); + ProviderInfo result = mContract.getProvider(requestWrongCerts); + + assertNull(result); + } + + public void testGetProvider_providerIsNonSystemAppCorrectCerts() + throws PackageManager.NameNotFoundException { + ProviderInfo info = setupPackageManager(); + + List<byte[]> certList = Arrays.asList(BYTE_ARRAY); + FontRequest requestRightCerts = new FontRequest( + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList)); + ProviderInfo result = mContract.getProvider(requestRightCerts); + + assertEquals(info, result); + } + + public void testGetProvider_providerIsNonSystemAppMoreCerts() + throws PackageManager.NameNotFoundException { + setupPackageManager(); + + byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); + List<byte[]> certList = Arrays.asList(wrongCert, BYTE_ARRAY); + FontRequest requestRightCerts = new FontRequest( + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList)); + ProviderInfo result = mContract.getProvider(requestRightCerts); + + // There is one too many certs, should fail as the set doesn't match. + assertNull(result); + } + + public void testGetProvider_providerIsNonSystemAppCorrectCertsSeveralSets() + throws PackageManager.NameNotFoundException { + ProviderInfo info = setupPackageManager(); + + List<List<byte[]>> certList = new ArrayList<>(); + byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT); + certList.add(Arrays.asList(wrongCert)); + certList.add(Arrays.asList(BYTE_ARRAY)); + FontRequest requestRightCerts = new FontRequest( + TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", certList); + ProviderInfo result = mContract.getProvider(requestRightCerts); + + assertEquals(info, result); + } + + public void testGetProvider_providerIsNonSystemAppWrongPackage() + throws PackageManager.NameNotFoundException { + setupPackageManager(); + + List<List<byte[]>> certList = new ArrayList<>(); + certList.add(Arrays.asList(BYTE_ARRAY)); + FontRequest requestRightCerts = new FontRequest( + TestFontsProvider.AUTHORITY, "com.wrong.package.name", "query", certList); + ProviderInfo result = mContract.getProvider(requestRightCerts); + + assertNull(result); + } + + private ProviderInfo setupPackageManager() + throws PackageManager.NameNotFoundException { + ProviderInfo info = new ProviderInfo(); + info.packageName = PACKAGE_NAME; + info.applicationInfo = new ApplicationInfo(); + when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info); + PackageInfo packageInfo = new PackageInfo(); + Signature signature = mock(Signature.class); + when(signature.toByteArray()).thenReturn(BYTE_ARRAY); + packageInfo.packageName = PACKAGE_NAME; + packageInfo.signatures = new Signature[] { signature }; + when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo); + return info; + } +} diff --git a/core/tests/coretests/src/android/provider/TestFontsProvider.java b/core/tests/coretests/src/android/provider/TestFontsProvider.java new file mode 100644 index 000000000000..6d40f37af548 --- /dev/null +++ b/core/tests/coretests/src/android/provider/TestFontsProvider.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 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.provider; + +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.ParcelFileDescriptor; + +import java.io.File; +import java.io.IOException; + +/** + * Provides a test Content Provider implementing {@link FontsContract}. + */ +public class TestFontsProvider extends ContentProvider { + static final String AUTHORITY = "android.provider.TestFontsProvider"; + static final int TTC_INDEX = 2; + static final String VARIATION_SETTINGS = "'wdth' 1"; + static final int STYLE = Typeface.BOLD; + + private ParcelFileDescriptor mPfd; + private boolean mReturnAllFields = true; + + /** + * Used by tests to switch whether all fields should be returned or not. + */ + void setReturnAllFields(boolean returnAllFields) { + mReturnAllFields = returnAllFields; + } + + @Override + public boolean onCreate() { + mPfd = createFontFile(); + return true; + } + + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, + @Nullable String[] selectionArgs, @Nullable String sortOrder) { + MatrixCursor cursor; + if (mReturnAllFields) { + cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID, + FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS, + FontsContract.Columns.STYLE }); + cursor.addRow(new Object[] { 1, TTC_INDEX, VARIATION_SETTINGS, STYLE }); + } else { + cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID }); + cursor.addRow(new Object[] { 1 }); + } + return cursor; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) { + try { + return mPfd.dup(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public String getType(@NonNull Uri uri) { + return "application/x-font-ttf"; + } + + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return null; + } + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, + @Nullable String[] selectionArgs) { + return 0; + } + + @Override + public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, + @Nullable String[] selectionArgs) { + return 0; + } + + private ParcelFileDescriptor createFontFile() { + try { + final File file = new File(getContext().getCacheDir(), "font.ttf"); + file.getParentFile().mkdirs(); + file.createNewFile(); + return ParcelFileDescriptor.open(file, MODE_READ_ONLY); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 52e18a9095cf..8b309035c013 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -186,8 +186,8 @@ public class Typeface { } // Downloaded font and it wasn't cached, request it again and return a // default font instead (nothing we can do now). - create(new FontRequest(family.getProviderAuthority(), family.getQuery()), - NO_OP_REQUEST_CALLBACK); + create(new FontRequest(family.getProviderAuthority(), family.getProviderPackage(), + family.getQuery()), NO_OP_REQUEST_CALLBACK); return DEFAULT; } diff --git a/graphics/java/android/graphics/fonts/FontRequest.java b/graphics/java/android/graphics/fonts/FontRequest.java index e50df6faa38e..c7a583056b76 100644 --- a/graphics/java/android/graphics/fonts/FontRequest.java +++ b/graphics/java/android/graphics/fonts/FontRequest.java @@ -18,24 +18,56 @@ package android.graphics.fonts; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.util.Base64; import com.android.internal.util.Preconditions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * Information about a font request that may be sent to a Font Provider. */ public final class FontRequest implements Parcelable { private final String mProviderAuthority; + private final String mProviderPackage; private final String mQuery; + private final List<List<byte[]>> mCertificates; + + /** + * @param providerAuthority The authority of the Font Provider to be used for the request. This + * should be a system installed app. + * @param providerPackage The package for the Font Provider to be used for the request. This is + * used to verify the identity of the provider. + * @param query The query to be sent over to the provider. Refer to your font provider's + * documentation on the format of this string. + */ + public FontRequest(@NonNull String providerAuthority, @NonNull String providerPackage, + @NonNull String query) { + mProviderAuthority = Preconditions.checkNotNull(providerAuthority); + mQuery = Preconditions.checkNotNull(query); + mProviderPackage = Preconditions.checkNotNull(providerPackage); + mCertificates = Collections.emptyList(); + } /** * @param providerAuthority The authority of the Font Provider to be used for the request. * @param query The query to be sent over to the provider. Refer to your font provider's - * documentation on the format of this string. + * documentation on the format of this string. + * @param providerPackage The package for the Font Provider to be used for the request. This is + * used to verify the identity of the provider. + * @param certificates The list of sets of hashes for the certificates the provider should be + * signed with. This is used to verify the identity of the provider. Each set in the + * list represents one collection of signature hashes. Refer to your font provider's + * documentation for these values. */ - public FontRequest(@NonNull String providerAuthority, @NonNull String query) { + public FontRequest(@NonNull String providerAuthority, @NonNull String providerPackage, + @NonNull String query, @NonNull List<List<byte[]>> certificates) { mProviderAuthority = Preconditions.checkNotNull(providerAuthority); + mProviderPackage = Preconditions.checkNotNull(providerPackage); mQuery = Preconditions.checkNotNull(query); + mCertificates = Preconditions.checkNotNull(certificates); } /** @@ -47,6 +79,14 @@ public final class FontRequest implements Parcelable { } /** + * Returns the selected font provider's package. This helps the system verify that the provider + * identified by the given authority is the one requested. + */ + public String getProviderPackage() { + return mProviderPackage; + } + + /** * Returns the query string. Refer to your font provider's documentation on the format of this * string. */ @@ -54,6 +94,14 @@ public final class FontRequest implements Parcelable { return mQuery; } + /** + * Returns the list of certificate sets given for this provider. This helps the system verify + * that the provider identified by the given authority is the one requested. + */ + public List<List<byte[]>> getCertificates() { + return mCertificates; + } + @Override public int describeContents() { return 0; @@ -62,12 +110,17 @@ public final class FontRequest implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mProviderAuthority); + dest.writeString(mProviderPackage); dest.writeString(mQuery); + dest.writeList(mCertificates); } private FontRequest(Parcel in) { mProviderAuthority = in.readString(); + mProviderPackage = in.readString(); mQuery = in.readString(); + mCertificates = new ArrayList<>(); + in.readList(mCertificates, null); } public static final Parcelable.Creator<FontRequest> CREATOR = @@ -85,9 +138,24 @@ public final class FontRequest implements Parcelable { @Override public String toString() { - return "FontRequest {" + StringBuilder builder = new StringBuilder(); + builder.append("FontRequest {" + "mProviderAuthority: " + mProviderAuthority + + ", mProviderPackage: " + mProviderPackage + ", mQuery: " + mQuery - + "}"; + + ", mCertificates:"); + for (int i = 0; i < mCertificates.size(); i++) { + builder.append(" ["); + List<byte[]> set = mCertificates.get(i); + for (int j = 0; j < set.size(); j++) { + builder.append(" \""); + byte[] array = set.get(j); + builder.append(Base64.encodeToString(array, Base64.DEFAULT)); + builder.append("\""); + } + builder.append(" ]"); + } + builder.append("}"); + return builder.toString(); } } diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp index 9f98241c6caa..9823a02dc847 100644 --- a/libs/hwui/BakedOpState.cpp +++ b/libs/hwui/BakedOpState.cpp @@ -31,7 +31,7 @@ static int computeClipSideFlags(const Rect& clip, const Rect& bounds) { } ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp, bool expandForStroke) { + const RecordedOp& recordedOp, bool expandForStroke, bool expandForPathTexture) { // resolvedMatrix = parentMatrix * localMatrix transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix); @@ -40,6 +40,8 @@ ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& s if (CC_UNLIKELY(expandForStroke)) { // account for non-hairline stroke clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f); + } else if (CC_UNLIKELY(expandForPathTexture)) { + clippedBounds.outset(1); } transform.mapRect(clippedBounds); if (CC_UNLIKELY(expandForStroke @@ -111,7 +113,7 @@ BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator, Snapshot& snapshot, const RecordedOp& recordedOp) { if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( - allocator, snapshot, recordedOp, false); + allocator, snapshot, recordedOp, false, false); if (bakedState->computedState.clippedBounds.isEmpty()) { // bounds are empty, so op is rejected allocator.rewindIfLastAlloc(bakedState); @@ -127,14 +129,14 @@ BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator, } BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator, - Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) { + Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior, + bool expandForPathTexture) { if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined) - ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style) - : true; + bool expandForStroke = (strokeBehavior == StrokeBehavior::Forced + || (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)); BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( - allocator, snapshot, recordedOp, expandForStroke); + allocator, snapshot, recordedOp, expandForStroke, expandForPathTexture); if (bakedState->computedState.clippedBounds.isEmpty()) { // bounds are empty, so op is rejected // NOTE: this won't succeed if a clip was allocated diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h index e1441fca5ee2..7b0b34f3d9c0 100644 --- a/libs/hwui/BakedOpState.h +++ b/libs/hwui/BakedOpState.h @@ -53,7 +53,7 @@ struct MergedBakedOpList { class ResolvedRenderState { public: ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp, bool expandForStroke); + const RecordedOp& recordedOp, bool expandForStroke, bool expandForPathTexture); // Constructor for unbounded ops *with* transform/clip ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, @@ -117,7 +117,8 @@ public: }; static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator, - Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior); + Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior, + bool expandForPathTexture); static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr); @@ -140,8 +141,8 @@ private: friend class LinearAllocator; BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp, bool expandForStroke) - : computedState(allocator, snapshot, recordedOp, expandForStroke) + const RecordedOp& recordedOp, bool expandForStroke, bool expandForPathTexture) + : computedState(allocator, snapshot, recordedOp, expandForStroke, expandForPathTexture) , alpha(snapshot.alpha) , roundRectClipState(snapshot.roundRectClipState) , op(&recordedOp) {} diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp index 1b57e290c198..86f9a5d73fd1 100644 --- a/libs/hwui/FrameBuilder.cpp +++ b/libs/hwui/FrameBuilder.cpp @@ -562,10 +562,11 @@ void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) { * for paint's style on the bounds being computed. */ BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId, - BakedOpState::StrokeBehavior strokeBehavior) { + BakedOpState::StrokeBehavior strokeBehavior, bool expandForPathTexture) { // Note: here we account for stroke when baking the op BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( - mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior); + mAllocator, *mCanvasState.writableSnapshot(), op, + strokeBehavior, expandForPathTexture); if (!bakedState) return nullptr; // quick rejected if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) { @@ -590,7 +591,10 @@ static batchid_t tessBatchId(const RecordedOp& op) { } void FrameBuilder::deferArcOp(const ArcOp& op) { - deferStrokeableOp(op, tessBatchId(op)); + // Pass true below since arcs have a tendency to draw outside their expected bounds within + // their path textures. Passing true makes it more likely that we'll scissor, instead of + // corrupting the frame by drawing outside of clip bounds. + deferStrokeableOp(op, tessBatchId(op), BakedOpState::StrokeBehavior::StyleDefined, true); } static bool hasMergeableClip(const BakedOpState& state) { @@ -748,7 +752,7 @@ static batchid_t textBatchId(const SkPaint& paint) { void FrameBuilder::deferTextOp(const TextOp& op) { BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( mAllocator, *mCanvasState.writableSnapshot(), op, - BakedOpState::StrokeBehavior::StyleDefined); + BakedOpState::StrokeBehavior::StyleDefined, false); if (!bakedState) return; // quick rejected batchid_t batchId = textBatchId(*(op.paint)); diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h index b9154435c1e5..46048f7125ce 100644 --- a/libs/hwui/FrameBuilder.h +++ b/libs/hwui/FrameBuilder.h @@ -211,7 +211,8 @@ private: } BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId, - BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined); + BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined, + bool expandForPathTexture = false); /** * Declares all FrameBuilder::deferXXXXOp() methods for every RecordedOp type. diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp index 0f8e0471556f..d51db2ebb169 100644 --- a/libs/hwui/tests/unit/BakedOpStateTests.cpp +++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp @@ -35,7 +35,7 @@ TEST(ResolvedRenderState, construct) { { // recorded with transform, no parent transform auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); - ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false); EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20); EXPECT_EQ(Rect(100, 200), state.clipRect()); EXPECT_EQ(Rect(40, 60, 100, 200), state.clippedBounds); // translated and also clipped @@ -44,7 +44,7 @@ TEST(ResolvedRenderState, construct) { { // recorded with transform and parent transform auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200)); - ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false); Matrix4 expectedTranslate; expectedTranslate.loadTranslate(20, 40, 0); @@ -70,14 +70,14 @@ TEST(ResolvedRenderState, computeLocalSpaceClip) { { // recorded with transform, no parent transform auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); - ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false); EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip()) << "Local clip rect should be 100x200, offset by -10,-20"; } { // recorded with transform + parent transform auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200)); - ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false, false); EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip()) << "Local clip rect should be 90x190, offset by -10,-20"; } @@ -170,7 +170,7 @@ TEST(ResolvedRenderState, construct_expandForStroke) { snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1); auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200)); - ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true, false); testCase.validator(state); } } @@ -234,7 +234,7 @@ TEST(BakedOpState, tryStrokeableOpConstruct) { RectOp rejectOp(Rect(100, 200), Matrix4::identity(), &clip, &paint); auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, - BakedOpState::StrokeBehavior::StyleDefined); + BakedOpState::StrokeBehavior::StyleDefined, false); EXPECT_EQ(nullptr, bakedState); EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op @@ -248,7 +248,7 @@ TEST(BakedOpState, tryStrokeableOpConstruct) { RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint); auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200)); auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, - BakedOpState::StrokeBehavior::StyleDefined); + BakedOpState::StrokeBehavior::StyleDefined, false); ASSERT_NE(nullptr, bakedState); EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds); @@ -263,7 +263,7 @@ TEST(BakedOpState, tryStrokeableOpConstruct) { RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint); auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200)); auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, - BakedOpState::StrokeBehavior::Forced); + BakedOpState::StrokeBehavior::Forced, false); ASSERT_NE(nullptr, bakedState); EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds); diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp index 95d9459e898c..a329980b7609 100644 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -171,6 +171,35 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleStroke) { EXPECT_EQ(1, renderer.getIndex()); } + +RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, arcStrokeClip) { + class ArcStrokeClipTestRenderer : public TestRendererBase { + public: + void onArcOp(const ArcOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(Rect(25, 25, 175, 175), op.unmappedBounds); + EXPECT_EQ(Rect(25, 25, 175, 175), state.computedState.clippedBounds); + EXPECT_EQ(OpClipSideFlags::Full, state.computedState.clipSideFlags) + << "Arc op clipped conservatively, since path texture may be expanded"; + } + }; + + auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.clipRect(25, 25, 175, 175, SkClipOp::kIntersect); + SkPaint aaPaint; + aaPaint.setAntiAlias(true); + canvas.drawArc(25, 25, 175, 175, 40, 180, true, aaPaint); + }); + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + ArcStrokeClipTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleRejection) { auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { diff --git a/packages/SystemUI/res/drawable/pip_expand.xml b/packages/SystemUI/res/drawable/pip_expand.xml new file mode 100644 index 000000000000..e34a95d257ac --- /dev/null +++ b/packages/SystemUI/res/drawable/pip_expand.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2017 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="60dp" + android:height="60dp" + android:viewportWidth="60" + android:viewportHeight="60"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M8,7.5v45c0,2.75,2.25,5,5,5h34.05c2.75,0,4.95-2.25,4.95-5v-45c0-2.75-2.2-5-4.95-5H13 +C10.25,2.5,8,4.75,8,7.5z +M13,6.5h34c0.55,0,1,0.45,1,1v45c0,0.55-0.45,1-1,1H13c-0.55,0-1-0.45-1-1v-45C12,6.95,12.45,6.5,13,6.5z" /> + <path + android:pathData="M60,0L0,0l0,60h60V0z" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M20.86,35h-2c-0.55,0-1,0.45-1,1v10.5c0,0.55,0.45,1,1,1h10.5c0.55,0,1-0.45,1-1v-2c0-0.55-0.45-1-1-1h-7.5 +V36C21.86,35.45,21.41,35,20.86,35z" /> + <path + android:fillColor="#FFFFFF" + android:pathData="M29.64,13.5v2c0,0.55,0.45,1,1,1h7.5V24c0,0.55,0.45,1,1,1h2c0.55,0,1-0.45,1-1V13.5c0-0.55-0.45-1-1-1 +h-10.5C30.09,12.5,29.64,12.95,29.64,13.5z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml index 0f5ca9bdf2fc..f38c8ff5da0c 100644 --- a/packages/SystemUI/res/layout/pip_menu_activity.xml +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -32,19 +32,32 @@ android:background="?android:selectableItemBackgroundBorderless" /> <FrameLayout + android:id="@+id/expand_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:layout_width="64dp" + android:layout_height="64dp" + android:layout_gravity="center" + android:contentDescription="@string/pip_phone_expand" + android:src="@drawable/pip_expand" + android:background="?android:selectableItemBackgroundBorderless" /> + </FrameLayout> + + <FrameLayout android:id="@+id/actions_container" android:layout_width="match_parent" android:layout_height="48dp" android:layout_gravity="bottom" - android:paddingStart="24dp" - android:paddingEnd="24dp" android:background="#66000000" android:visibility="invisible"> <LinearLayout - android:id="@+id/actions" + android:id="@+id/actions_group" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_horizontal" - android:orientation="horizontal" /> + android:orientation="horizontal" + android:divider="@android:color/transparent" + android:showDividers="middle" /> </FrameLayout> </FrameLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3512761afbd2..f331d8782a2d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -731,6 +731,13 @@ <!-- The size of the PIP drag-to-dismiss target. --> <dimen name="pip_dismiss_target_size">48dp</dimen> + <!-- The shortest-edge size of the expanded PiP. --> + <dimen name="pip_expanded_shortest_edge_size">160dp</dimen> + + <!-- The padding between actions in the PiP in landscape Note that the PiP does not reflect + the configuration of the device, so we can't use -land resources. --> + <dimen name="pip_between_action_padding_land">8dp</dimen> + <dimen name="default_gear_space">18dp</dimen> <dimen name="cell_overlay_padding">18dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 5d57daaaf4a1..e38f922c23b4 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -53,7 +53,7 @@ public class DozeFactory { DozeMachine machine = new DozeMachine( DozeScreenStatePreventingAdapter.wrapIfNeeded(dozeService, params), - params, + config, wakeLock); machine.setParts(new DozeMachine.Part[]{ createDozeTriggers(context, sensorManager, host, config, params, handler, wakeLock, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index c9eb790c695e..c852b491feba 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -17,11 +17,12 @@ package com.android.systemui.doze; import android.annotation.MainThread; +import android.os.UserHandle; import android.util.Log; import android.view.Display; +import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.util.Preconditions; -import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; import java.io.PrintWriter; @@ -95,16 +96,17 @@ public class DozeMachine { private final Service mDozeService; private final DozeFactory.WakeLock mWakeLock; - private final DozeParameters mParams; + private final AmbientDisplayConfiguration mConfig; private Part[] mParts; private final ArrayList<State> mQueuedRequests = new ArrayList<>(); private State mState = State.UNINITIALIZED; private boolean mWakeLockHeldForCurrentState = false; - public DozeMachine(Service service, DozeParameters params, DozeFactory.WakeLock wakeLock) { + public DozeMachine(Service service, AmbientDisplayConfiguration config, + DozeFactory.WakeLock wakeLock) { mDozeService = service; - mParams = params; + mConfig = config; mWakeLock = wakeLock; } @@ -267,7 +269,7 @@ public class DozeMachine { switch (state) { case INITIALIZED: case DOZE_PULSE_DONE: - transitionTo(mParams.getAlwaysOn() + transitionTo(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE); break; default: diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java new file mode 100644 index 000000000000..7a1849ed741e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 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.pip.phone; + +import static android.view.WindowManager.INPUT_CONSUMER_PIP; + +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.IWindowManager; +import android.view.MotionEvent; + +import java.io.PrintWriter; + +/** + * Manages the input consumer that allows the SystemUI to control the PiP. + */ +public class InputConsumerController { + + private static final String TAG = InputConsumerController.class.getSimpleName(); + + /** + * Listener interface for callers to subscribe to touch events. + */ + public interface TouchListener { + boolean onTouchEvent(MotionEvent ev); + } + + /** + * Input handler used for the PiP input consumer. + */ + private final class PipInputEventReceiver extends InputEventReceiver { + + public PipInputEventReceiver(InputChannel inputChannel, Looper looper) { + super(inputChannel, looper); + } + + @Override + public void onInputEvent(InputEvent event) { + boolean handled = true; + try { + // To be implemented for input handling over Pip windows + if (mListener != null && event instanceof MotionEvent) { + MotionEvent ev = (MotionEvent) event; + handled = mListener.onTouchEvent(ev); + } + } finally { + finishInputEvent(event, handled); + } + } + } + + private IWindowManager mWindowManager; + + private PipInputEventReceiver mInputEventReceiver; + private TouchListener mListener; + + public InputConsumerController(IWindowManager windowManager) { + mWindowManager = windowManager; + registerInputConsumer(); + } + + /** + * Sets the touch listener. + */ + public void setTouchListener(TouchListener listener) { + mListener = listener; + } + + /** + * Registers the input consumer. + */ + public void registerInputConsumer() { + if (mInputEventReceiver == null) { + final InputChannel inputChannel = new InputChannel(); + try { + mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); + mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel); + } catch (RemoteException e) { + Log.e(TAG, "Failed to create PIP input consumer", e); + } + mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper()); + } + } + + /** + * Unregisters the input consumer. + */ + public void unregisterInputConsumer() { + if (mInputEventReceiver != null) { + try { + mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); + } catch (RemoteException e) { + Log.e(TAG, "Failed to destroy PIP input consumer", e); + } + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } + } + + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java index 714b263e40cc..ecc2fadf5ae2 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -53,6 +53,7 @@ public class PipManager implements BasePipManager { private final PinnedStackListener mPinnedStackListener = new PinnedStackListener(); + private InputConsumerController mInputConsumerController; private PipMenuActivityController mMenuController; private PipMediaController mMediaController; private PipTouchHandler mTouchHandler; @@ -68,6 +69,7 @@ public class PipManager implements BasePipManager { } mTouchHandler.onActivityPinned(); mMediaController.onActivityPinned(); + mMenuController.onActivityPinned(); } @Override @@ -120,9 +122,10 @@ public class PipManager implements BasePipManager { @Override public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, - boolean fromImeAdjustement) { + Rect animatingBounds, boolean fromImeAdjustement) { mHandler.post(() -> { - mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, fromImeAdjustement); + mTouchHandler.onMovementBoundsChanged(insetBounds, normalBounds, animatingBounds, + fromImeAdjustement); }); } @@ -151,11 +154,12 @@ public class PipManager implements BasePipManager { } SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener); + mInputConsumerController = new InputConsumerController(mWindowManager); mMediaController = new PipMediaController(context, mActivityManager); - mMenuController = new PipMenuActivityController(context, mActivityManager, mWindowManager, - mMediaController); - mTouchHandler = new PipTouchHandler(context, mMenuController, mActivityManager, - mWindowManager); + mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController, + mInputConsumerController); + mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController, + mInputConsumerController); } /** @@ -178,6 +182,7 @@ public class PipManager implements BasePipManager { public void dump(PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG); + mInputConsumerController.dump(pw, innerPrefix); mMenuController.dump(pw, innerPrefix); mTouchHandler.dump(pw, innerPrefix); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 4e28061dc21b..65de22e21775 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -40,8 +40,9 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager.LayoutParams; +import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.TextView; +import android.widget.LinearLayout; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -57,8 +58,9 @@ public class PipMenuActivity extends Activity { private static final String TAG = "PipMenuActivity"; public static final int MESSAGE_SHOW_MENU = 1; - public static final int MESSAGE_HIDE_MENU = 2; - public static final int MESSAGE_UPDATE_ACTIONS = 3; + public static final int MESSAGE_POKE_MENU = 2; + public static final int MESSAGE_HIDE_MENU = 3; + public static final int MESSAGE_UPDATE_ACTIONS = 4; private static final long INITIAL_DISMISS_DELAY = 2000; private static final long POST_INTERACTION_DISMISS_DELAY = 1500; @@ -67,7 +69,9 @@ public class PipMenuActivity extends Activity { private boolean mMenuVisible; private final List<RemoteAction> mActions = new ArrayList<>(); private View mMenuContainer; + private LinearLayout mActionsGroup; private View mDismissButton; + private int mBetweenActionPaddingLand; private ObjectAnimator mMenuContainerAnimator; @@ -83,6 +87,9 @@ public class PipMenuActivity extends Activity { case MESSAGE_SHOW_MENU: showMenu(); break; + case MESSAGE_POKE_MENU: + cancelDelayedFinish(); + break; case MESSAGE_HIDE_MENU: hideMenu(); break; @@ -127,6 +134,9 @@ public class PipMenuActivity extends Activity { mDismissButton.setOnClickListener((v) -> { dismissPip(); }); + mActionsGroup = (LinearLayout) findViewById(R.id.actions_group); + mBetweenActionPaddingLand = getResources().getDimensionPixelSize( + R.dimen.pip_between_action_padding_land); notifyActivityCallback(mMessenger); showMenu(); @@ -139,6 +149,11 @@ public class PipMenuActivity extends Activity { } @Override + public void onUserInteraction() { + repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); + } + + @Override protected void onUserLeaveHint() { super.onUserLeaveHint(); @@ -164,11 +179,6 @@ public class PipMenuActivity extends Activity { } @Override - public void onUserInteraction() { - repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); - } - - @Override public boolean dispatchTouchEvent(MotionEvent ev) { // On the first action outside the window, hide the menu switch (ev.getAction()) { @@ -177,13 +187,16 @@ public class PipMenuActivity extends Activity { break; case MotionEvent.ACTION_DOWN: mDownPosition.set(ev.getX(), ev.getY()); + mDownDelta.set(0f, 0f); break; case MotionEvent.ACTION_MOVE: mDownDelta.set(ev.getX() - mDownPosition.x, ev.getY() - mDownPosition.y); if (mDownDelta.length() > mViewConfig.getScaledTouchSlop() && mMenuVisible) { - hideMenu(); - mMenuVisible = false; + // Restore the input consumer and let that drive the movement of this menu + notifyRegisterInputConsumer(); + cancelDelayedFinish(); } + break; } return super.dispatchTouchEvent(ev); } @@ -219,17 +232,21 @@ public class PipMenuActivity extends Activity { } }); mMenuContainerAnimator.start(); + } else { + repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); } } private void hideMenu() { - hideMenu(null /* animationFinishedRunnable */); + hideMenu(null /* animationFinishedRunnable */, true /* notifyMenuVisibility */); } - private void hideMenu(final Runnable animationFinishedRunnable) { + private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility) { if (mMenuVisible) { cancelDelayedFinish(); - notifyMenuVisibility(false); + if (notifyMenuVisibility) { + notifyMenuVisibility(false); + } mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, mMenuContainer.getAlpha(), 0f); mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT); @@ -253,26 +270,30 @@ public class PipMenuActivity extends Activity { } private void updateActionViews() { + ViewGroup expandContainer = (ViewGroup) findViewById(R.id.expand_container); ViewGroup actionsContainer = (ViewGroup) findViewById(R.id.actions_container); actionsContainer.setOnTouchListener((v, ev) -> { // Do nothing, prevent click through to parent return true; }); + int actionsContainerHeight = 0; if (mActions.isEmpty()) { actionsContainer.setVisibility(View.INVISIBLE); } else { actionsContainer.setVisibility(View.VISIBLE); - ViewGroup actionsGroup = (ViewGroup) findViewById(R.id.actions); - if (actionsGroup != null) { - actionsGroup.removeAllViews(); + if (mActionsGroup != null) { + mActionsGroup.removeAllViews(); // Recreate the layout + final View decorView = getWindow().getDecorView(); + final boolean isLandscapePip = decorView.getMeasuredWidth() + > decorView.getMeasuredHeight(); final LayoutInflater inflater = LayoutInflater.from(this); for (int i = 0; i < mActions.size(); i++) { final RemoteAction action = mActions.get(i); final ImageView actionView = (ImageView) inflater.inflate( - R.layout.pip_menu_action, actionsGroup, false); + R.layout.pip_menu_action, mActionsGroup, false); action.getIcon().loadDrawableAsync(this, d -> { d.setTint(Color.WHITE); actionView.setImageDrawable(d); @@ -285,10 +306,27 @@ public class PipMenuActivity extends Activity { Log.w(TAG, "Failed to send action", e); } }); - actionsGroup.addView(actionView); + if (isLandscapePip && i > 0) { + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) + actionView.getLayoutParams(); + lp.leftMargin = mBetweenActionPaddingLand; + } + mActionsGroup.addView(actionView); } } + actionsContainerHeight = actionsContainer.getLayoutParams().height; } + + // Update the expand container margin to account for the existence of the action container + ((FrameLayout.LayoutParams) expandContainer.getLayoutParams()).bottomMargin = + actionsContainerHeight; + expandContainer.requestLayout(); + } + + private void notifyRegisterInputConsumer() { + Message m = Message.obtain(); + m.what = PipMenuActivityController.MESSAGE_REGISTER_INPUT_CONSUMER; + sendMessage(m, "Could not notify controller to register input consumer"); } private void notifyMenuVisibility(boolean visible) { @@ -300,10 +338,12 @@ public class PipMenuActivity extends Activity { } private void expandPip() { + // Do not notify menu visibility when hiding the menu, the controller will do this when it + // handles the message hideMenu(() -> { sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP, "Could not notify controller to expand PIP"); - }); + }, false /* notifyMenuVisibility */); } private void minimizePip() { @@ -312,10 +352,12 @@ public class PipMenuActivity extends Activity { } private void dismissPip() { + // Do not notify menu visibility when hiding the menu, the controller will do this when it + // handles the message hideMenu(() -> { sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP, "Could not notify controller to dismiss PIP"); - }); + }, false /* notifyMenuVisibility */); } private void notifyActivityCallback(Messenger callback) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index 91115d034671..0b1c3ecc47bd 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -57,6 +57,7 @@ public class PipMenuActivityController { public static final int MESSAGE_MINIMIZE_PIP = 102; public static final int MESSAGE_DISMISS_PIP = 103; public static final int MESSAGE_UPDATE_ACTIVITY_CALLBACK = 104; + public static final int MESSAGE_REGISTER_INPUT_CONSUMER = 105; /** * A listener interface to receive notification on changes in PIP. @@ -64,8 +65,11 @@ public class PipMenuActivityController { public interface Listener { /** * Called when the PIP menu visibility changes. + * + * @param menuVisible whether or not the menu is visible + * @param resize whether or not to resize the PiP with the visibility change */ - void onPipMenuVisibilityChanged(boolean visible); + void onPipMenuVisibilityChanged(boolean menuVisible, boolean resize); /** * Called when the PIP requested to be expanded. @@ -85,13 +89,13 @@ public class PipMenuActivityController { private Context mContext; private IActivityManager mActivityManager; - private IWindowManager mWindowManager; private PipMediaController mMediaController; + private InputConsumerController mInputConsumerController; private ArrayList<Listener> mListeners = new ArrayList<>(); private ParceledListSlice mAppActions; private ParceledListSlice mMediaActions; - private boolean mVisible; + private boolean mMenuVisible; private Messenger mToActivityMessenger; private Messenger mMessenger = new Messenger(new Handler() { @@ -100,13 +104,14 @@ public class PipMenuActivityController { switch (msg.what) { case MESSAGE_MENU_VISIBILITY_CHANGED: { boolean visible = msg.arg1 > 0; - onMenuVisibilityChanged(visible); + onMenuVisibilityChanged(visible, true /* resize */); break; } case MESSAGE_EXPAND_PIP: { mListeners.forEach(l -> l.onPipExpand()); - // Preemptively mark the menu as invisible once we expand the PiP - onMenuVisibilityChanged(false); + // Preemptively mark the menu as invisible once we expand the PiP, but don't + // resize as we will be animating the stack + onMenuVisibilityChanged(false, false /* resize */); break; } case MESSAGE_MINIMIZE_PIP: { @@ -115,15 +120,20 @@ public class PipMenuActivityController { } case MESSAGE_DISMISS_PIP: { mListeners.forEach(l -> l.onPipDismiss()); - // Preemptively mark the menu as invisible once we dismiss the PiP - onMenuVisibilityChanged(false); + // Preemptively mark the menu as invisible once we dismiss the PiP, but don't + // resize as we'll be removing the stack in place + onMenuVisibilityChanged(false, false /* resize */); + break; + } + case MESSAGE_REGISTER_INPUT_CONSUMER: { + mInputConsumerController.registerInputConsumer(); break; } case MESSAGE_UPDATE_ACTIVITY_CALLBACK: { mToActivityMessenger = msg.replyTo; // Mark the menu as invisible once the activity finishes as well if (mToActivityMessenger == null) { - onMenuVisibilityChanged(false); + onMenuVisibilityChanged(false, true /* resize */); } break; } @@ -140,11 +150,19 @@ public class PipMenuActivityController { }; public PipMenuActivityController(Context context, IActivityManager activityManager, - IWindowManager windowManager, PipMediaController mediaController) { + PipMediaController mediaController, InputConsumerController inputConsumerController) { mContext = context; mActivityManager = activityManager; - mWindowManager = windowManager; mMediaController = mediaController; + mInputConsumerController = inputConsumerController; + } + + public void onActivityPinned() { + if (!mMenuVisible) { + // If the menu is not visible, then re-register the input consumer if it is not already + // registered + mInputConsumerController.registerInputConsumer(); + } } /** @@ -192,6 +210,21 @@ public class PipMenuActivityController { } /** + * Pokes the menu, indicating that the user is interacting with it. + */ + public void pokeMenu() { + if (mToActivityMessenger != null) { + Message m = Message.obtain(); + m.what = PipMenuActivity.MESSAGE_POKE_MENU; + try { + mToActivityMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify poke menu", e); + } + } + } + + /** * Hides the menu activity. */ public void hideMenu() { @@ -207,6 +240,13 @@ public class PipMenuActivityController { } /** + * @return whether the menu is currently visible. + */ + public boolean isMenuVisible() { + return mMenuVisible; + } + + /** * Sets the menu actions to the actions provided by the current PiP activity. */ public void setAppActions(ParceledListSlice appActions) { @@ -250,9 +290,14 @@ public class PipMenuActivityController { /** * Handles changes in menu visibility. */ - private void onMenuVisibilityChanged(boolean visible) { - mListeners.forEach(l -> l.onPipMenuVisibilityChanged(visible)); - if (visible != mVisible) { + private void onMenuVisibilityChanged(boolean visible, boolean resize) { + if (visible) { + mInputConsumerController.unregisterInputConsumer(); + } else { + mInputConsumerController.registerInputConsumer(); + } + if (visible != mMenuVisible) { + mListeners.forEach(l -> l.onPipMenuVisibilityChanged(visible, resize)); if (visible) { // Once visible, start listening for media action changes. This call will trigger // the menu actions to be updated again. @@ -263,13 +308,13 @@ public class PipMenuActivityController { mMediaController.removeListener(mMediaActionListener); } } - mVisible = visible; + mMenuVisible = visible; } public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); - pw.println(innerPrefix + "mVisible=" + mVisible); + pw.println(innerPrefix + "mMenuVisible=" + mMenuVisible); pw.println(innerPrefix + "mListeners=" + mListeners.size()); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index ed0a37fed52d..20c1136a5998 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -57,10 +57,11 @@ public class PipMotionHelper { private static final int DEFAULT_MOVE_STACK_DURATION = 225; private static final int SNAP_STACK_DURATION = 225; private static final int DISMISS_STACK_DURATION = 375; - private static final int SHRINK_STACK_FROM_MENU_DURATION = 175; - private static final int EXPAND_STACK_TO_MENU_DURATION = 175; - private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 225; + private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; + private static final int EXPAND_STACK_TO_MENU_DURATION = 250; + private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300; private static final int MINIMIZE_STACK_MAX_DURATION = 200; + private static final int IME_SHIFT_DURATION = 300; // The fraction of the stack width that the user has to drag offscreen to minimize the PiP private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.2f; @@ -217,19 +218,12 @@ public class PipMotionHelper { /** * Animates the PiP to the minimized state, slightly offscreen. */ - Rect animateToClosestMinimizedState(Rect movementBounds, - final PipMenuActivityController menuController) { + Rect animateToClosestMinimizedState(Rect movementBounds) { cancelAnimations(); Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds); if (!mBounds.equals(toBounds)) { mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN, mUpdateBoundsListener); - mBoundsAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - menuController.hideMenu(); - } - }); mBoundsAnimator.start(); } return toBounds; @@ -274,9 +268,7 @@ public class PipMotionHelper { Rect expandedMovementBounds) { float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), movementBounds); mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction); - mBoundsAnimator = createAnimationToBounds(mBounds, expandedBounds, - EXPAND_STACK_TO_MENU_DURATION, FAST_OUT_SLOW_IN, mUpdateBoundsListener); - mBoundsAnimator.start(); + resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION); return savedSnapFraction; } @@ -284,15 +276,25 @@ public class PipMotionHelper { * Animates the PiP from the expanded state to the normal state after the menu is hidden. */ void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction, - Rect normalMovementBounds) { - if (savedSnapFraction >= 0f) { - mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction); - mBoundsAnimator = createAnimationToBounds(mBounds, normalBounds, - SHRINK_STACK_FROM_MENU_DURATION, FAST_OUT_SLOW_IN, mUpdateBoundsListener); - mBoundsAnimator.start(); - } else { - animateToClosestSnapTarget(normalMovementBounds); + Rect normalMovementBounds, Rect currentMovementBounds, boolean minimized) { + if (savedSnapFraction < 0f) { + // If there are no saved snap fractions, then just use the current bounds + savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), + currentMovementBounds); + } + mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction); + if (minimized) { + normalBounds = getClosestMinimizedBounds(normalBounds, normalMovementBounds); } + resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION); + } + + /** + * Animates the PiP to offset it from the IME. + */ + void animateToIMEOffset(Rect toBounds) { + cancelAnimations(); + resizeAndAnimatePipUnchecked(toBounds, IME_SHIFT_DURATION); } /** @@ -317,18 +319,6 @@ public class PipMotionHelper { } /** - * Animates the PiP to some given bounds. - */ - void animateToBounds(Rect toBounds) { - cancelAnimations(); - if (!mBounds.equals(toBounds)) { - mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, - DEFAULT_MOVE_STACK_DURATION, FAST_OUT_LINEAR_IN, mUpdateBoundsListener); - mBoundsAnimator.start(); - } - } - - /** * Cancels all existing animations. */ void cancelAnimations() { @@ -365,7 +355,32 @@ public class PipMotionHelper { mActivityManager.resizePinnedStack(toBounds, null /* tempPinnedTaskBounds */); mBounds.set(toBounds); } catch (RemoteException e) { - Log.e(TAG, "Could not move pinned stack to bounds: " + toBounds, e); + Log.e(TAG, "Could not resize pinned stack to bounds: " + toBounds, e); + } + }); + } + } + + /** + * Directly resizes the PiP to the given {@param bounds}. + */ + private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) { + if (!toBounds.equals(mBounds)) { + mHandler.post(() -> { + try { + StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + if (stackInfo == null) { + // In the case where we've already re-expanded or dismissed the PiP, then + // just skip the resize + return; + } + + mActivityManager.resizeStack(PINNED_STACK_ID, toBounds, + false /* allowResizeInDockedMode */, true /* preserveWindows */, + true /* animate */, duration); + mBounds.set(toBounds); + } catch (RemoteException e) { + Log.e(TAG, "Could not animate resize pinned stack to bounds: " + toBounds, e); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 4100b66b07b6..010522d2818d 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -16,27 +16,24 @@ package com.android.systemui.pip.phone; -import static android.view.WindowManager.INPUT_CONSUMER_PIP; - import android.app.IActivityManager; import android.content.Context; +import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.os.Handler; -import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import android.util.Size; import android.view.IPinnedStackController; import android.view.IWindowManager; -import android.view.InputChannel; -import android.view.InputEvent; -import android.view.InputEventReceiver; import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.PipSnapAlgorithm; +import com.android.systemui.R; import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.PrintWriter; @@ -59,12 +56,10 @@ public class PipTouchHandler { private final Context mContext; private final IActivityManager mActivityManager; - private final IWindowManager mWindowManager; private final ViewConfiguration mViewConfig; private final PipMenuListener mMenuListener = new PipMenuListener(); private IPinnedStackController mPinnedStackController; - private PipInputEventReceiver mInputEventReceiver; private final PipMenuActivityController mMenuController; private final PipDismissViewController mDismissViewController; private final PipSnapAlgorithm mSnapAlgorithm; @@ -77,6 +72,7 @@ public class PipTouchHandler { private Rect mNormalMovementBounds = new Rect(); private Rect mExpandedBounds = new Rect(); private Rect mExpandedMovementBounds = new Rect(); + private int mExpandedShortestEdgeSize; private Handler mHandler = new Handler(); private Runnable mShowDismissAffordance = new Runnable() { @@ -89,9 +85,8 @@ public class PipTouchHandler { }; // Behaviour states - private boolean mIsTappingThrough; - private boolean mIsMinimized; private boolean mIsMenuVisible; + private boolean mIsMinimized; private boolean mIsImeShowing; private int mImeHeight; private float mSavedSnapFraction = -1f; @@ -106,36 +101,12 @@ public class PipTouchHandler { private final Rect mTmpBounds = new Rect(); /** - * Input handler used for Pip windows. - */ - private final class PipInputEventReceiver extends InputEventReceiver { - - public PipInputEventReceiver(InputChannel inputChannel, Looper looper) { - super(inputChannel, looper); - } - - @Override - public void onInputEvent(InputEvent event) { - boolean handled = true; - try { - // To be implemented for input handling over Pip windows - if (event instanceof MotionEvent) { - MotionEvent ev = (MotionEvent) event; - handled = handleTouchEvent(ev); - } - } finally { - finishInputEvent(event, handled); - } - } - } - - /** * A listener for the PIP menu activity. */ private class PipMenuListener implements PipMenuActivityController.Listener { @Override - public void onPipMenuVisibilityChanged(boolean visible) { - setMenuVisibilityState(visible); + public void onPipMenuVisibilityChanged(boolean menuVisible, boolean resize) { + setMenuVisibilityState(menuVisible, resize); } @Override @@ -148,7 +119,7 @@ public class PipTouchHandler { @Override public void onPipMinimize() { setMinimizedStateInternal(true); - mMotionHelper.animateToClosestMinimizedState(mMovementBounds, mMenuController); + mMotionHelper.animateToClosestMinimizedState(mMovementBounds); } @Override @@ -159,13 +130,13 @@ public class PipTouchHandler { } } - public PipTouchHandler(Context context, PipMenuActivityController menuController, - IActivityManager activityManager, IWindowManager windowManager) { + public PipTouchHandler(Context context, IActivityManager activityManager, + PipMenuActivityController menuController, + InputConsumerController inputConsumerController) { // Initialize the Pip input consumer mContext = context; mActivityManager = activityManager; - mWindowManager = windowManager; mViewConfig = ViewConfiguration.get(context); mMenuController = menuController; mMenuController.addListener(mMenuListener); @@ -178,7 +149,11 @@ public class PipTouchHandler { }; mMotionHelper = new PipMotionHelper(mContext, mActivityManager, mSnapAlgorithm, mFlingAnimationUtils); - registerInputConsumer(); + mExpandedShortestEdgeSize = context.getResources().getDimensionPixelSize( + R.dimen.pip_expanded_shortest_edge_size); + + // Register the listener for input consumer touch events + inputConsumerController.setTouchListener(this::handleTouchEvent); } public void setTouchEnabled(boolean enabled) { @@ -187,9 +162,8 @@ public class PipTouchHandler { public void onActivityPinned() { // Reset some states once we are pinned - if (mIsTappingThrough) { - mIsTappingThrough = false; - registerInputConsumer(); + if (mIsMenuVisible) { + mIsMenuVisible = false; } if (mIsMinimized) { setMinimizedStateInternal(false); @@ -206,15 +180,21 @@ public class PipTouchHandler { mImeHeight = imeHeight; } - public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, + public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect animatingBounds, boolean fromImeAdjustement) { // Re-calculate the expanded bounds mNormalBounds = normalBounds; Rect normalMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mNormalBounds, insetBounds, normalMovementBounds, mIsImeShowing ? mImeHeight : 0); - // TODO: Figure out the expanded size policy - mExpandedBounds = new Rect(normalBounds); + + // Calculate the expanded size + float aspectRatio = (float) normalBounds.width() / normalBounds.height(); + Point displaySize = new Point(); + mContext.getDisplay().getRealSize(displaySize); + Size expandedSize = mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, + mExpandedShortestEdgeSize, displaySize.x, displaySize.y); + mExpandedBounds.set(0, 0, expandedSize.getWidth(), expandedSize.getHeight()); Rect expandedMovementBounds = new Rect(); mSnapAlgorithm.getMovementBounds(mExpandedBounds, insetBounds, expandedMovementBounds, mIsImeShowing ? mImeHeight : 0); @@ -227,7 +207,7 @@ public class PipTouchHandler { // Defer the update of the current movement bounds until after the user finishes // touching the screen } else { - final Rect bounds = new Rect(mMotionHelper.getBounds()); + final Rect bounds = new Rect(animatingBounds); final Rect toMovementBounds = mIsMenuVisible ? expandedMovementBounds : normalMovementBounds; @@ -247,7 +227,7 @@ public class PipTouchHandler { bounds.offsetTo(bounds.left, toMovementBounds.bottom); } } - mMotionHelper.animateToBounds(bounds); + mMotionHelper.animateToIMEOffset(bounds); } } @@ -255,7 +235,7 @@ public class PipTouchHandler { // above mNormalMovementBounds = normalMovementBounds; mExpandedMovementBounds = expandedMovementBounds; - updateMovementBounds(); + updateMovementBounds(mIsMenuVisible); } private boolean handleTouchEvent(MotionEvent ev) { @@ -287,7 +267,7 @@ public class PipTouchHandler { case MotionEvent.ACTION_UP: { // Update the movement bounds again if the state has changed since the user started // dragging (ie. when the IME shows) - updateMovementBounds(); + updateMovementBounds(mIsMenuVisible); for (PipTouchGesture gesture : mGestures) { if (gesture.onUp(mTouchState)) { @@ -302,38 +282,7 @@ public class PipTouchHandler { break; } } - return !mIsTappingThrough; - } - - /** - * Registers the input consumer. - */ - private void registerInputConsumer() { - if (mInputEventReceiver == null) { - final InputChannel inputChannel = new InputChannel(); - try { - mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); - mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel); - } catch (RemoteException e) { - Log.e(TAG, "Failed to create PIP input consumer", e); - } - mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper()); - } - } - - /** - * Unregisters the input consumer. - */ - private void unregisterInputConsumer() { - if (mInputEventReceiver != null) { - try { - mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP); - } catch (RemoteException e) { - Log.e(TAG, "Failed to destroy PIP input consumer", e); - } - mInputEventReceiver.dispose(); - mInputEventReceiver = null; - } + return !mIsMenuVisible; } /** @@ -379,34 +328,30 @@ public class PipTouchHandler { /** * Sets the menu visibility. */ - void setMenuVisibilityState(boolean isMenuVisible) { - if (!isMenuVisible) { - mIsTappingThrough = false; - registerInputConsumer(); - } else { - unregisterInputConsumer(); - } - MetricsLogger.visibility(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU, - isMenuVisible); - - if (isMenuVisible != mIsMenuVisible) { - if (isMenuVisible) { - // Save the current snap fraction and if we do not drag or move the PiP, then - // we store back to this snap fraction. Otherwise, we'll reset the snap - // fraction and snap to the closest edge - Rect expandedBounds = new Rect(mExpandedBounds); + void setMenuVisibilityState(boolean menuVisible, boolean resize) { + if (menuVisible) { + // Save the current snap fraction and if we do not drag or move the PiP, then + // we store back to this snap fraction. Otherwise, we'll reset the snap + // fraction and snap to the closest edge + Rect expandedBounds = new Rect(mExpandedBounds); + if (resize) { mSavedSnapFraction = mMotionHelper.animateToExpandedState(expandedBounds, mMovementBounds, mExpandedMovementBounds); - } else { - // Try and restore the PiP to the closest edge, using the saved snap fraction - // if possible + } + } else { + // Try and restore the PiP to the closest edge, using the saved snap fraction + // if possible + if (resize) { Rect normalBounds = new Rect(mNormalBounds); mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction, - mNormalMovementBounds); + mNormalMovementBounds, mMovementBounds, mIsMinimized); } - mIsMenuVisible = isMenuVisible; - updateMovementBounds(); + mSavedSnapFraction = -1f; } + mIsMenuVisible = menuVisible; + updateMovementBounds(menuVisible); + MetricsLogger.visibility(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_MENU, + menuVisible); } /** @@ -427,6 +372,12 @@ public class PipTouchHandler { return; } + // If the menu is still visible, and we aren't minimized, then just poke the menu + // so that it will timeout after the user stops touching it + if (mMenuController.isMenuVisible() && !mIsMinimized) { + mMenuController.pokeMenu(); + } + if (ENABLE_DRAG_TO_DISMISS) { mDismissViewController.createDismissTarget(); mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY); @@ -495,21 +446,35 @@ public class PipTouchHandler { } finally { mDismissViewController.destroyDismissTarget(); } + if (touchState.isDragging()) { PointF vel = mTouchState.getVelocity(); if (!mIsMinimized && (mMotionHelper.shouldMinimizePip() || isHorizontalFlingTowardsCurrentEdge(vel))) { // Pip should be minimized setMinimizedStateInternal(true); - mMotionHelper.animateToClosestMinimizedState(mMovementBounds, mMenuController); + if (mMenuController.isMenuVisible()) { + // If the user dragged the expanded PiP to the edge, then hiding the menu + // will trigger the PiP to be scaled back to the normal size with the + // minimize offset adjusted + mMenuController.hideMenu(); + } else { + mMotionHelper.animateToClosestMinimizedState(mMovementBounds); + } return true; } if (mIsMinimized) { - // If we're dragging and it wasn't a minimize gesture - // then we shouldn't be minimized. + // If we're dragging and it wasn't a minimize gesture then we shouldn't be + // minimized. setMinimizedStateInternal(false); } + // If the menu is still visible, and we aren't minimized, then just poke the menu + // so that it will timeout after the user stops touching it + if (mMenuController.isMenuVisible()) { + mMenuController.showMenu(); + } + final float velocity = PointF.length(vel.x, vel.y); if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds); @@ -520,9 +485,8 @@ public class PipTouchHandler { // This was a tap, so no longer minimized mMotionHelper.animateToClosestSnapTarget(mMovementBounds); setMinimizedStateInternal(false); - } else if (!mIsTappingThrough) { + } else if (!mIsMenuVisible) { mMenuController.showMenu(); - mIsTappingThrough = true; } else { mMotionHelper.expandPip(); } @@ -559,8 +523,8 @@ public class PipTouchHandler { /** * Updates the current movement bounds based on whether the menu is currently visible. */ - private void updateMovementBounds() { - mMovementBounds = mIsMenuVisible + private void updateMovementBounds(boolean isExpanded) { + mMovementBounds = isExpanded ? mExpandedMovementBounds : mNormalMovementBounds; } @@ -573,9 +537,8 @@ public class PipTouchHandler { pw.println(innerPrefix + "mNormalMovementBounds=" + mNormalMovementBounds); pw.println(innerPrefix + "mExpandedBounds=" + mExpandedBounds); pw.println(innerPrefix + "mExpandedMovementBounds=" + mExpandedMovementBounds); - pw.println(innerPrefix + "mIsTappingThrough=" + mIsTappingThrough); - pw.println(innerPrefix + "mIsMinimized=" + mIsMinimized); pw.println(innerPrefix + "mIsMenuVisible=" + mIsMenuVisible); + pw.println(innerPrefix + "mIsMinimized=" + mIsMinimized); pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing); pw.println(innerPrefix + "mImeHeight=" + mImeHeight); pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction); diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index d506669f74a8..9a8974d18905 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -197,7 +197,7 @@ public class PipManager implements BasePipManager { @Override public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, - boolean fromImeAdjustement) { + Rect animatingBounds, boolean fromImeAdjustement) { mHandler.post(() -> { mDefaultPipBounds.set(normalBounds); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 602c3dfde73e..fc3bb43e4fed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -927,12 +927,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (areGutsExposed()) { return false; } - if (mIsSummaryWithChildren) { - return true; - } - NotificationContentView showingLayout = getShowingLayout(); - NotificationHeaderView notificationHeader = showingLayout.getVisibleNotificationHeader(); - return notificationHeader != null; + return getVisibleNotificationHeader() != null; } /** @@ -1720,6 +1715,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override public boolean isContentExpandable() { + if (mIsSummaryWithChildren && !mShowingPublic) { + return true; + } NotificationContentView showingLayout = getShowingLayout(); return showingLayout.isContentExpandable(); } @@ -1987,6 +1985,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (canViewBeDismissed()) { info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); } + boolean expandable = mShowingPublic; + boolean isExpanded = false; + if (!expandable) { + if (mIsSummaryWithChildren) { + expandable = true; + if (!mIsLowPriority || isExpanded()) { + isExpanded = isGroupExpanded(); + } + } else { + expandable = mPrivateLayout.isContentExpandable(); + isExpanded = isExpanded(); + } + } + if (expandable) { + if (isExpanded) { + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); + } else { + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); + } + } } @Override @@ -1999,6 +2017,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { NotificationStackScrollLayout.performDismiss(this, mGroupManager, true /* fromAccessibility */); return true; + case AccessibilityNodeInfo.ACTION_COLLAPSE: + case AccessibilityNodeInfo.ACTION_EXPAND: + mExpandClickListener.onClick(this); + return true; } return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 74e65fbd2432..8f160dc0384e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -131,6 +131,7 @@ public class NotificationContentView extends FrameLayout { private boolean mIconsVisible; private int mClipBottomAmount; private boolean mIsLowPriority; + private boolean mIsContentExpandable; public NotificationContentView(Context context, AttributeSet attrs) { @@ -949,7 +950,7 @@ public class NotificationContentView extends FrameLayout { } public boolean isContentExpandable() { - return mExpandedChild != null; + return mIsContentExpandable; } public void setDark(boolean dark, boolean fade, long delay) { @@ -1198,10 +1199,10 @@ public class NotificationContentView extends FrameLayout { if (mExpandedChild != null && mExpandedChild.getHeight() != 0) { if ((!mIsHeadsUp && !mHeadsUpAnimatingAway) || mHeadsUpChild == null || mContainingNotification.isOnKeyguard()) { - if (mExpandedChild.getHeight() == mContractedChild.getHeight()) { + if (mExpandedChild.getHeight() <= mContractedChild.getHeight()) { expandable = false; } - } else if (mExpandedChild.getHeight() == mHeadsUpChild.getHeight()) { + } else if (mExpandedChild.getHeight() <= mHeadsUpChild.getHeight()) { expandable = false; } } @@ -1214,6 +1215,7 @@ public class NotificationContentView extends FrameLayout { if (mHeadsUpChild != null) { mHeadsUpWrapper.updateExpandability(expandable, mExpandClickListener); } + mIsContentExpandable = expandable; } public NotificationHeaderView getNotificationHeader() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 4a2ec88c7518..7b2e9979fb33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.phone; import android.content.Context; -import android.os.Build; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; @@ -25,6 +24,7 @@ import android.text.TextUtils; import android.util.MathUtils; import android.util.SparseBooleanArray; +import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.systemui.R; import java.io.PrintWriter; @@ -32,14 +32,15 @@ import java.io.PrintWriter; public class DozeParameters { private static final int MAX_DURATION = 60 * 1000; public static final String DOZE_SENSORS_WAKE_UP_FULLY = "doze_sensors_wake_up_fully"; - public static final boolean ALWAYS_ON_AVAILABLE = Build.IS_DEBUGGABLE; private final Context mContext; + private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; private static IntInOutMatcher sPickupSubtypePerformsProxMatcher; public DozeParameters(Context context) { mContext = context; + mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); } public void dump(PrintWriter pw) { @@ -58,8 +59,7 @@ public class DozeParameters { pw.print(" getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold()); pw.print(" getPickupSubtypePerformsProxCheck(): ");pw.println( dumpPickupSubtypePerformsProxCheck()); - if (ALWAYS_ON_AVAILABLE) { - pw.print(" getAlwaysOn(): "); pw.println(getAlwaysOn()); + if (mAmbientDisplayConfiguration.alwaysOnAvailable()) { pw.print(" getSensorsWakeUpFully(): "); pw.println(getSensorsWakeUpFully()); } } @@ -119,13 +119,11 @@ public class DozeParameters { } public boolean getAlwaysOn() { - return ALWAYS_ON_AVAILABLE - && Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.DOZE_ALWAYS_ON, 0, UserHandle.USER_CURRENT) != 0; + return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); } public boolean getSensorsWakeUpFully() { - return ALWAYS_ON_AVAILABLE + return mAmbientDisplayConfiguration.alwaysOnAvailable() && Settings.Secure.getIntForUser(mContext.getContentResolver(), DOZE_SENSORS_WAKE_UP_FULLY, 1, UserHandle.USER_CURRENT) != 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 19be586710ce..005b701c0d52 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -715,6 +715,7 @@ public class StatusBar extends SystemUI implements DemoMode, private NotificationIconAreaController mNotificationIconAreaController; private ConfigurationListener mConfigurationListener; private InflationExceptionHandler mInflationExceptionHandler = this::handleInflationException; + private boolean mReinflateNotificationsOnUserSwitched; private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) { final int N = array.size(); @@ -1274,16 +1275,10 @@ public class StatusBar extends SystemUI implements DemoMode, protected void onDensityOrFontScaleChanged() { // start old BaseStatusBar.onDensityOrFontScaleChanged(). - ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); - for (int i = 0; i < activeNotifications.size(); i++) { - Entry entry = activeNotifications.get(i); - boolean exposedGuts = mNotificationGutsExposed != null - && entry.row.getGuts() == mNotificationGutsExposed; - entry.row.onDensityOrFontScaleChanged(); - if (exposedGuts) { - mNotificationGutsExposed = entry.row.getGuts(); - bindGuts(entry.row, mGutsMenuItem); - } + if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) { + updateNotificationsOnDensityOrFontScaleChanged(); + } else { + mReinflateNotificationsOnUserSwitched = true; } // end old BaseStatusBar.onDensityOrFontScaleChanged(). mScrimController.onDensityOrFontScaleChanged(); @@ -1308,6 +1303,20 @@ public class StatusBar extends SystemUI implements DemoMode, } } + private void updateNotificationsOnDensityOrFontScaleChanged() { + ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications(); + for (int i = 0; i < activeNotifications.size(); i++) { + Entry entry = activeNotifications.get(i); + boolean exposedGuts = mNotificationGutsExposed != null + && entry.row.getGuts() == mNotificationGutsExposed; + entry.row.onDensityOrFontScaleChanged(); + if (exposedGuts) { + mNotificationGutsExposed = entry.row.getGuts(); + bindGuts(entry.row, mGutsMenuItem); + } + } + } + private void inflateSignalClusters() { reinflateSignalCluster(mKeyguardStatusBar); } @@ -3598,7 +3607,12 @@ public class StatusBar extends SystemUI implements DemoMode, if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); animateCollapsePanels(); updatePublicMode(); - updateNotifications(); + mNotificationData.filterAndSort(); + if (mReinflateNotificationsOnUserSwitched) { + updateNotificationsOnDensityOrFontScaleChanged(); + mReinflateNotificationsOnUserSwitched = false; + } + updateNotificationShade(); clearCurrentMediaNotification(); setLockscreenUser(newUserId); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java index 209b439e876c..e7bce708e48f 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java @@ -26,11 +26,11 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.R; import com.android.systemui.plugins.PluginPrefs; -import com.android.systemui.statusbar.phone.DozeParameters; public class TunerFragment extends PreferenceFragment { @@ -65,7 +65,7 @@ public class TunerFragment extends PreferenceFragment { if (!PluginPrefs.hasPlugins(getContext())) { getPreferenceScreen().removePreference(findPreference(KEY_PLUGINS)); } - if (!DozeParameters.ALWAYS_ON_AVAILABLE) { + if (!alwaysOnAvailable()) { getPreferenceScreen().removePreference(findPreference(KEY_DOZE)); } @@ -77,6 +77,10 @@ public class TunerFragment extends PreferenceFragment { } } + private boolean alwaysOnAvailable() { + return new AmbientDisplayConfiguration(getContext()).alwaysOnAvailable(); + } + @Override public void onResume() { super.onResume(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index 32afee9744d5..267580e5b35b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -28,8 +28,9 @@ import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -42,7 +43,7 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.view.Display; -import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.internal.hardware.AmbientDisplayConfiguration; import org.junit.Before; import org.junit.Test; @@ -56,17 +57,17 @@ public class DozeMachineTest { private DozeServiceFake mServiceFake; private WakeLockFake mWakeLockFake; - private DozeParameters mParamsMock; + private AmbientDisplayConfiguration mConfigMock; private DozeMachine.Part mPartMock; @Before public void setUp() { mServiceFake = new DozeServiceFake(); mWakeLockFake = new WakeLockFake(); - mParamsMock = mock(DozeParameters.class); + mConfigMock = mock(AmbientDisplayConfiguration.class); mPartMock = mock(DozeMachine.Part.class); - mMachine = new DozeMachine(mServiceFake, mParamsMock, mWakeLockFake); + mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake); mMachine.setParts(new DozeMachine.Part[]{mPartMock}); } @@ -82,7 +83,7 @@ public class DozeMachineTest { @Test @UiThreadTest public void testInitialize_goesToDoze() { - when(mParamsMock.getAlwaysOn()).thenReturn(false); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); mMachine.requestState(INITIALIZED); @@ -93,7 +94,7 @@ public class DozeMachineTest { @Test @UiThreadTest public void testInitialize_goesToAod() { - when(mParamsMock.getAlwaysOn()).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); mMachine.requestState(INITIALIZED); @@ -104,7 +105,7 @@ public class DozeMachineTest { @Test @UiThreadTest public void testPulseDone_goesToDoze() { - when(mParamsMock.getAlwaysOn()).thenReturn(false); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(false); mMachine.requestState(INITIALIZED); mMachine.requestState(DOZE_REQUEST_PULSE); mMachine.requestState(DOZE_PULSING); @@ -118,7 +119,7 @@ public class DozeMachineTest { @Test @UiThreadTest public void testPulseDone_goesToAoD() { - when(mParamsMock.getAlwaysOn()).thenReturn(true); + when(mConfigMock.alwaysOnEnabled(anyInt())).thenReturn(true); mMachine.requestState(INITIALIZED); mMachine.requestState(DOZE_REQUEST_PULSE); mMachine.requestState(DOZE_PULSING); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java new file mode 100644 index 000000000000..bf741ec41ac7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ExpandableNotificationRowTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 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; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.Notification; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.service.notification.StatusBarNotification; +import android.support.test.InstrumentationRegistry; +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.RemoteViews; + +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.InflationException; +import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper; +import com.android.systemui.statusbar.notification.NotificationInflater; +import com.android.systemui.statusbar.notification.NotificationViewWrapper; +import com.android.systemui.statusbar.phone.NotificationGroupManager; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ExpandableNotificationRowTest { + + private Context mContext; + private ExpandableNotificationRow mRow; + private NotificationGroupManager mGroupManager = new NotificationGroupManager(); + private int mId; + + @Before + @UiThreadTest + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + mRow = createNotification(); + } + + private ExpandableNotificationRow createNotification() { + LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate( + R.layout.status_bar_notification_row, + null, false); + row.setGroupManager(mGroupManager); + Notification publicVersion = new Notification.Builder(mContext).setSmallIcon( + R.drawable.ic_person) + .setCustomContentView(new RemoteViews(mContext.getPackageName(), + R.layout.custom_view_dark)) + .build(); + Notification notification = new Notification.Builder(mContext).setSmallIcon( + R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text") + .setPublicVersion(publicVersion) + .build(); + UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser()); + StatusBarNotification sbn = new StatusBarNotification("com.android.systemui", + "com.android.systemui", mId++, null, 1000, + 2000, notification, mUser, null, System.currentTimeMillis()); + NotificationData.Entry entry = new NotificationData.Entry(sbn); + entry.row = row; + try { + entry.createIcons(mContext, sbn); + row.updateNotification(entry); + } catch (InflationException e) { + throw new RuntimeException(e.getMessage()); + } + return row; + } + + @Test + public void testGroupSummaryNotShowingIconWhenPublic() { + mRow.setSensitive(true, true); + mRow.addChildNotification(createNotification()); + mRow.addChildNotification(createNotification()); + mRow.setHideSensitive(true, false, 0, 0); + Assert.assertTrue(mRow.isSummaryWithChildren()); + Assert.assertFalse(mRow.isShowingIcon()); + } + +} diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 2b219e637aed..44de0bdd9409 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -3486,6 +3486,37 @@ message MetricsEvent { // ACTION: Settings > Battery > Menu > Apps Toggle ACTION_SETTINGS_MENU_BATTERY_APPS_TOGGLE = 852; + // ACTION: Settings > Any preference is changed + ACTION_SETTINGS_PREFERENCE_CHANGE = 853; + + // FIELD: The name of preference when it is changed in Settings + FIELD_SETTINGS_PREFERENCE_CHANGE_NAME = 854; + + // FIELD: The new value of preference when it is changed in Settings + FIELD_SETTINGS_PREFERENCE_CHANGE_VALUE = 855; + + // OPEN: Notification channel created. CLOSE: Notification channel deleted. UPDATE: notification + // channel updated + // PACKAGE: the package the channel belongs too + // CATEGORY: NOTIFICATION + // OS: O + ACTION_NOTIFICATION_CHANNEL = 856; + + // Tagged data for notification channel. String. + FIELD_NOTIFICATION_CHANNEL_ID = 857; + + // Tagged data for notification channel. int. + FIELD_NOTIFICATION_CHANNEL_IMPORTANCE = 858; + + // OPEN: Notification channel group created. + // PACKAGE: the package the group belongs to + // CATEGORY: NOTIFICATION + // OS: O + ACTION_NOTIFICATION_CHANNEL_GROUP = 859; + + // Tagged data for notification channel group. String. + FIELD_NOTIFICATION_CHANNEL_GROUP_ID = 860; + // ---- End O Constants, all O constants go above this line ---- // Add new aosp constants above this line. diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java index 9d2f75080bb6..667bf71363da 100644 --- a/rs/java/android/renderscript/Element.java +++ b/rs/java/android/renderscript/Element.java @@ -1071,7 +1071,6 @@ public class Element extends BaseObj { mSize += mElements[ct].mSize * mArraySizes[ct]; } updateVisibleSubElements(); - guard.open("destroy"); } Element(long id, RenderScript rs, DataType dt, DataKind dk, boolean norm, int size) { @@ -1091,7 +1090,6 @@ public class Element extends BaseObj { mKind = dk; mNormalized = norm; mVectorSize = size; - guard.open("destroy"); } Element(long id, RenderScript rs) { diff --git a/rs/java/android/renderscript/Type.java b/rs/java/android/renderscript/Type.java index 9252898781f4..dc2378596d00 100644 --- a/rs/java/android/renderscript/Type.java +++ b/rs/java/android/renderscript/Type.java @@ -227,7 +227,6 @@ public class Type extends BaseObj { Type(long id, RenderScript rs) { super(id, rs); - guard.open("destroy"); } @Override diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 82d86ffd95cb..df6148e653d7 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -641,6 +641,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (appCount == 0 && mEnable) { disableBleScanMode(); } + if (appCount == 0 && !mEnableExternal) { + sendBrEdrDownCallback(); + } return appCount; } @@ -696,7 +699,14 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return; } - if (isBleAppPresent() == false) { + if (isBleAppPresent()) { + // Need to stay at BLE ON. Disconnect all Gatt connections + try { + mBluetoothGatt.unregAll(); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to disconnect all apps.", e); + } + } else { try { mBluetoothLock.readLock().lock(); if (mBluetooth != null) mBluetooth.onBrEdrDown(); @@ -705,14 +715,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } finally { mBluetoothLock.readLock().unlock(); } - } else { - // Need to stay at BLE ON. Disconnect all Gatt connections - try { - mBluetoothGatt.unregAll(); - } catch (RemoteException e) { - Slog.e(TAG, "Unable to disconnect all apps.", e); - } } + } public boolean enableNoAutoConnect(String packageName) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 0bc9d19ab1ec..16ed2768de89 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -395,16 +395,6 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31; - /** - * Indicates a caller has requested to have its callback invoked with - * the latest LinkProperties or NetworkCapabilities. - * - * arg1 = UID of caller - * obj = NetworkRequest - */ - private static final int EVENT_REQUEST_LINKPROPERTIES = 32; - private static final int EVENT_REQUEST_NETCAPABILITIES = 33; - /** Handler thread used for both of the handlers below. */ @VisibleForTesting protected final HandlerThread mHandlerThread; @@ -2565,34 +2555,6 @@ public class ConnectivityService extends IConnectivityManager.Stub return nri; } - private void handleRequestCallbackUpdate(NetworkRequest request, int callingUid, - String description, int callbackType) { - final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, description); - if (nri == null) return; - - final NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); - // The network that is satisfying this request may have changed since - // the application requested the update. - // - // - If the request is no longer satisfied, don't send any updates. - // - If the request is satisfied by a different network, it is the - // caller's responsibility to check that the Network object in the - // callback matches the network that was returned in the last - // onAvailable() callback for this request. - if (nai == null) return; - callCallbackForRequest(nri, nai, callbackType, 0); - } - - private void handleRequestLinkProperties(NetworkRequest request, int callingUid) { - handleRequestCallbackUpdate(request, callingUid, - "request LinkProperties", ConnectivityManager.CALLBACK_IP_CHANGED); - } - - private void handleRequestNetworkCapabilities(NetworkRequest request, int callingUid) { - handleRequestCallbackUpdate(request, callingUid, - "request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED); - } - private void handleTimedOutNetworkRequest(final NetworkRequestInfo nri) { if (mNetworkRequests.get(nri.request) != null && mNetworkForRequestId.get( nri.request.requestId) == null) { @@ -2986,12 +2948,6 @@ public class ConnectivityService extends IConnectivityManager.Stub handleMobileDataAlwaysOn(); break; } - case EVENT_REQUEST_LINKPROPERTIES: - handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1); - break; - case EVENT_REQUEST_NETCAPABILITIES: - handleRequestNetworkCapabilities((NetworkRequest) msg.obj, msg.arg1); - break; // Sent by KeepaliveTracker to process an app request on the state machine thread. case NetworkAgent.CMD_START_PACKET_KEEPALIVE: { mKeepaliveTracker.handleStartKeepalive(msg); @@ -4352,22 +4308,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override - public void requestLinkProperties(NetworkRequest networkRequest) { - ensureNetworkRequestHasType(networkRequest); - if (networkRequest.type == NetworkRequest.Type.LISTEN) return; - mHandler.sendMessage(mHandler.obtainMessage( - EVENT_REQUEST_LINKPROPERTIES, getCallingUid(), 0, networkRequest)); - } - - @Override - public void requestNetworkCapabilities(NetworkRequest networkRequest) { - ensureNetworkRequestHasType(networkRequest); - if (networkRequest.type == NetworkRequest.Type.LISTEN) return; - mHandler.sendMessage(mHandler.obtainMessage( - EVENT_REQUEST_NETCAPABILITIES, getCallingUid(), 0, networkRequest)); - } - - @Override public void releaseNetworkRequest(NetworkRequest networkRequest) { ensureNetworkRequestHasType(networkRequest); mHandler.sendMessage(mHandler.obtainMessage( @@ -4879,7 +4819,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!nr.isListen()) continue; if (nai.satisfies(nr) && !nai.isSatisfyingRequest(nr.requestId)) { nai.addRequest(nr); - notifyNetworkCallback(nai, nri); + notifyNetworkAvailable(nai, nri); } } } @@ -5061,7 +5001,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // do this after the default net is switched, but // before LegacyTypeTracker sends legacy broadcasts - for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri); + for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri); // Linger any networks that are no longer needed. This should be done after sending the // available callback for newNetwork. @@ -5224,7 +5164,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) { - NetworkInfo.State state = newInfo.getState(); + final NetworkInfo.State state = newInfo.getState(); NetworkInfo oldInfo = null; final int oldScore = networkAgent.getCurrentScore(); synchronized (networkAgent) { @@ -5351,15 +5291,27 @@ public class ConnectivityService extends IConnectivityManager.Stub sendUpdatedScoreToFactories(nai); } - // notify only this one new request of the current state - protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) { - int notifyType = ConnectivityManager.CALLBACK_AVAILABLE; + // Notify only this one new request of the current state. Transfer all the + // current state by calling NetworkCapabilities and LinkProperties callbacks + // so that callers can be guaranteed to have as close to atomicity in state + // transfer as can be supported by this current API. + protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) { mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri); - if (nri.mPendingIntent == null) { - callCallbackForRequest(nri, nai, notifyType, 0); - } else { - sendPendingIntentForRequest(nri, nai, notifyType); + if (nri.mPendingIntent != null) { + sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE); + // Attempt no subsequent state pushes where intents are involved. + return; + } + + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, 0); + // Whether a network is currently suspended is also an important + // element of state to be transferred (it would not otherwise be + // delivered by any currently available mechanism). + if (nai.networkInfo.getState() == NetworkInfo.State.SUSPENDED) { + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_SUSPENDED, 0); } + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_CAP_CHANGED, 0); + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_IP_CHANGED, 0); } private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) { diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index 0a9610f36abb..d9073f6cbf55 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -2198,18 +2198,26 @@ public class LockSettingsService extends ILockSettings.Stub { try { // Managed profile should have escrow enabled if (mUserManager.getUserInfo(userId).isManagedProfile()) { + Slog.i(TAG, "Managed profile can have escrow token"); return; } DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); // Devices with Device Owner should have escrow enabled on all users. if (dpm.getDeviceOwnerComponentOnAnyUser() != null) { + Slog.i(TAG, "Corp-owned device can have escrow token"); + return; + } + // We could also have a profile owner on the given (non-managed) user for unicorn cases + if (dpm.getProfileOwnerAsUser(userId) != null) { + Slog.i(TAG, "User with profile owner can have escrow token"); return; } // If the device is yet to be provisioned (still in SUW), there is still // a chance that Device Owner will be set on the device later, so postpone // disabling escrow token for now. if (!dpm.isDeviceProvisioned()) { + Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned"); return; } // Disable escrow token permanently on all other device/user types. diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 6e138a161e1c..bd7086165a07 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2770,7 +2770,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // while pausing because that changes the focused stack and may prevent the new // starting activity from resuming. if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE - && !r.supportsPictureInPictureWhilePausing) { + && (r.state == RESUMED || !r.supportsPictureInPictureWhilePausing)) { // Move the home stack forward if the task we just moved to the pinned stack // was launched from home so home should be visible behind it. moveHomeStackToFront(reason); diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 39e3758393df..60357c2befb9 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -77,6 +77,7 @@ import com.android.internal.util.StateMachine; import com.android.server.connectivity.tethering.IControlsTethering; import com.android.server.connectivity.tethering.IPv6TetheringCoordinator; import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices; +import com.android.server.connectivity.tethering.OffloadController; import com.android.server.connectivity.tethering.TetheringConfiguration; import com.android.server.connectivity.tethering.TetherInterfaceStateMachine; import com.android.server.connectivity.tethering.UpstreamNetworkMonitor; @@ -146,6 +147,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable)); private final StateMachine mTetherMasterSM; + private final OffloadController mOffloadController; private final UpstreamNetworkMonitor mUpstreamNetworkMonitor; private String mCurrentUpstreamIface; @@ -176,6 +178,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper); mTetherMasterSM.start(); + mOffloadController = new OffloadController(mTetherMasterSM.getHandler()); mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK); @@ -1205,6 +1208,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering protected void handleNewUpstreamNetworkState(NetworkState ns) { mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns); + mOffloadController.setUpstreamLinkProperties( + (ns != null) ? ns.linkProperties : null); } } @@ -1361,12 +1366,14 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering class TetherModeAliveState extends TetherMasterUtilState { final SimChangeListener simChange = new SimChangeListener(mContext); boolean mTryCell = true; + @Override public void enter() { // TODO: examine if we should check the return value. turnOnMasterTetherSettings(); // may transition us out simChange.startListening(); mUpstreamNetworkMonitor.start(); + mOffloadController.start(); // Better try something first pass or crazy tests cases will fail. chooseUpstreamType(true); @@ -1375,6 +1382,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering @Override public void exit() { + mOffloadController.stop(); unrequestUpstreamMobileConnection(); mUpstreamNetworkMonitor.stop(); simChange.stopListening(); diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java new file mode 100644 index 000000000000..220e7514facc --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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.connectivity.tethering; + +import android.net.LinkProperties; +import android.os.Handler; +import android.util.Log; + +/** + * A wrapper around hardware offload interface. + * + * @hide + */ +public class OffloadController { + private static final String TAG = OffloadController.class.getSimpleName(); + + private final Handler mHandler; + private LinkProperties mUpstreamLinkProperties; + + public OffloadController(Handler h) { + mHandler = h; + } + + public void start() { + // TODO: initOffload() and configure callbacks to be handled on our + // preferred Handler. + Log.d(TAG, "tethering offload not supported"); + } + + public void stop() { + // TODO: stopOffload(). + mUpstreamLinkProperties = null; + } + + public void setUpstreamLinkProperties(LinkProperties lp) { + // TODO: setUpstreamParameters(). + mUpstreamLinkProperties = lp; + } +} diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 6106093e4008..62099293b31c 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -186,6 +186,7 @@ public class UpstreamNetworkMonitor { switch (callbackType) { case CALLBACK_LISTEN_ALL: break; + case CALLBACK_TRACK_DEFAULT: if (mDefaultNetworkCallback == null) { // The callback was unregistered in the interval between @@ -198,11 +199,9 @@ public class UpstreamNetworkMonitor { // These request*() calls can be deleted post oag/339444. return; } - - cm().requestNetworkCapabilities(mDefaultNetworkCallback); - cm().requestLinkProperties(mDefaultNetworkCallback); mCurrentDefault = network; break; + case CALLBACK_MOBILE_REQUEST: if (mMobileNetworkCallback == null) { // The callback was unregistered in the interval between @@ -211,13 +210,8 @@ public class UpstreamNetworkMonitor { // // Clean-up of this network entry is deferred to the // handling of onLost() by other callbacks. - // - // These request*() calls can be deleted post oag/339444. return; } - - cm().requestNetworkCapabilities(mMobileNetworkCallback); - cm().requestLinkProperties(mMobileNetworkCallback); break; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0e767daf76c8..be8aaf02db41 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2792,6 +2792,7 @@ public class NotificationManagerService extends SystemService { dump.put("bans", mRankingHelper.dumpBansJson(filter)); dump.put("ranking", mRankingHelper.dumpJson(filter)); dump.put("stats", mUsageStats.dumpJson(filter)); + dump.put("channels", mRankingHelper.dumpChannelsJson(filter)); } catch (JSONException e) { e.printStackTrace(); } diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 3016b17db7b9..d751a2258dc6 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -778,9 +778,9 @@ public final class NotificationRecord { .addTaggedData(MetricsEvent.NOTIFICATION_TAG, sbn.getTag()); } return mLogMaker - .setCategory(MetricsEvent.VIEW_UNKNOWN) - .setType(MetricsEvent.TYPE_UNKNOWN) - .setSubtype(0) + .clearCategory() + .clearType() + .clearSubtype() .clearTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX) .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS, getLifespanMs(now)) .addTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS, getFreshnessMs(now)) diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index bb0742adf465..4cabe4e23ba4 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -19,6 +19,8 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import com.android.internal.util.Preconditions; import android.app.Notification; @@ -30,6 +32,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; +import android.metrics.LogMaker; import android.os.Build; import android.os.UserHandle; import android.provider.Settings; @@ -485,6 +488,12 @@ public class RankingHelper implements RankingConfig { if (r == null) { throw new IllegalArgumentException("Invalid package"); } + LogMaker lm = new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP) + .setType(MetricsProto.MetricsEvent.TYPE_UPDATE) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID, + group.getId()) + .setPackageName(pkg); + MetricsLogger.action(lm); r.groups.put(group.getId(), group); updateConfig(); } @@ -507,14 +516,20 @@ public class RankingHelper implements RankingConfig { if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) { throw new IllegalArgumentException("NotificationChannelGroup doesn't exist"); } + if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) { + throw new IllegalArgumentException("Reserved id"); + } NotificationChannel existing = r.channels.get(channel.getId()); // Keep existing settings if (existing != null) { if (existing.isDeleted()) { existing.setDeleted(false); - updateConfig(); } + + MetricsLogger.action(getChannelLog(channel, pkg)); + + updateConfig(); return; } if (channel.getImportance() < NotificationManager.IMPORTANCE_NONE @@ -538,6 +553,8 @@ public class RankingHelper implements RankingConfig { Notification.AUDIO_ATTRIBUTES_DEFAULT); } r.channels.put(channel.getId(), channel); + MetricsLogger.action(getChannelLog(channel, pkg).setType( + MetricsProto.MetricsEvent.TYPE_OPEN)); updateConfig(); } @@ -565,6 +582,8 @@ public class RankingHelper implements RankingConfig { updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); } r.channels.put(updatedChannel.getId(), updatedChannel); + + MetricsLogger.action(getChannelLog(updatedChannel, pkg)); updateConfig(); } @@ -612,6 +631,7 @@ public class RankingHelper implements RankingConfig { } // Assistant cannot change the group + MetricsLogger.action(getChannelLog(channel, pkg)); r.channels.put(channel.getId(), channel); updateConfig(); } @@ -661,6 +681,9 @@ public class RankingHelper implements RankingConfig { if (channel != null) { channel.setDeleted(true); } + LogMaker lm = getChannelLog(channel, pkg); + lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE); + MetricsLogger.action(lm); } @Override @@ -932,6 +955,49 @@ public class RankingHelper implements RankingConfig { return packageBans; } + /** + * Dump only the channel information as structured JSON for the stats collector. + * + * This is intentionally redundant with {#link dumpJson} because the old + * scraper will expect this format. + * + * @param filter + * @return + */ + public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) { + JSONArray channels = new JSONArray(); + Map<String, Integer> packageChannels = getPackageChannels(); + for(Entry<String, Integer> channelCount : packageChannels.entrySet()) { + final String packageName = channelCount.getKey(); + if (filter == null || filter.matches(packageName)) { + JSONObject channelCountJson = new JSONObject(); + try { + channelCountJson.put("packageName", packageName); + channelCountJson.put("channelCount", channelCount.getValue()); + } catch (JSONException e) { + e.printStackTrace(); + } + channels.put(channelCountJson); + } + } + return channels; + } + + private Map<String, Integer> getPackageChannels() { + ArrayMap<String, Integer> packageChannels = new ArrayMap<>(); + for (int i = 0; i < mRecords.size(); i++) { + final Record r = mRecords.valueAt(i); + int channelCount = 0; + for (int j = 0; j < r.channels.size();j++) { + if (!r.channels.valueAt(j).isDeleted()) { + channelCount++; + } + } + packageChannels.put(r.pkg, channelCount); + } + return packageChannels; + } + public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList) { if (pkgList == null || pkgList.length == 0) { @@ -979,6 +1045,16 @@ public class RankingHelper implements RankingConfig { } } + private LogMaker getChannelLog(NotificationChannel channel, String pkg) { + return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL) + .setType(MetricsProto.MetricsEvent.TYPE_UPDATE) + .setPackageName(pkg) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID, + channel.getId()) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, + channel.getImportance()); + } + private static class Record { static int UNKNOWN_UID = UserHandle.USER_NULL; diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index a692559129ad..1af541d6587b 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -53,9 +53,11 @@ import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.util.ConcurrentUtils; import com.android.server.FgThread; import com.android.server.IoThread; import com.android.server.LocalServices; +import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.pm.Installer; import com.android.server.pm.UserManagerService; @@ -74,6 +76,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -219,6 +222,8 @@ public final class OverlayManagerService extends SystemService { private final AtomicBoolean mPersistSettingsScheduled = new AtomicBoolean(false); + private Future<?> mInitCompleteSignal; + public OverlayManagerService(@NonNull final Context context, @NonNull final Installer installer) { super(context); @@ -230,28 +235,29 @@ public final class OverlayManagerService extends SystemService { mSettings = new OverlayManagerSettings(); mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings, getDefaultOverlayPackages()); + mInitCompleteSignal = SystemServerInitThreadPool.get().submit(() -> { + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(ACTION_PACKAGE_ADDED); + packageFilter.addAction(ACTION_PACKAGE_CHANGED); + packageFilter.addAction(ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme("package"); + getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, + packageFilter, null, null); + + final IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(ACTION_USER_REMOVED); + getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL, + userFilter, null, null); + + restoreSettings(); + onSwitchUser(UserHandle.USER_SYSTEM); + schedulePersistSettings(); - final IntentFilter packageFilter = new IntentFilter(); - packageFilter.addAction(ACTION_PACKAGE_ADDED); - packageFilter.addAction(ACTION_PACKAGE_CHANGED); - packageFilter.addAction(ACTION_PACKAGE_REMOVED); - packageFilter.addDataScheme("package"); - getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, - packageFilter, null, null); - - final IntentFilter userFilter = new IntentFilter(); - userFilter.addAction(ACTION_USER_REMOVED); - getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL, - userFilter, null, null); - - restoreSettings(); - onSwitchUser(UserHandle.USER_SYSTEM); - schedulePersistSettings(); - - mSettings.addChangeListener(new OverlayChangeListener()); + mSettings.addChangeListener(new OverlayChangeListener()); - publishBinderService(Context.OVERLAY_SERVICE, mService); - publishLocalService(OverlayManagerService.class, this); + publishBinderService(Context.OVERLAY_SERVICE, mService); + publishLocalService(OverlayManagerService.class, this); + }, "Init OverlayManagerService"); } @Override @@ -260,6 +266,15 @@ public final class OverlayManagerService extends SystemService { } @Override + public void onBootPhase(int phase) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + ConcurrentUtils.waitForFutureNoInterrupt(mInitCompleteSignal, + "Wait for OverlayManagerService init"); + mInitCompleteSignal = null; + } + } + + @Override public void onSwitchUser(final int newUserId) { // ensure overlays in the settings are up-to-date, and propagate // any asset changes to the rest of the system diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java index 837b69e5f7f3..cd0e6ccb2eb4 100644 --- a/services/core/java/com/android/server/wm/BoundsAnimationController.java +++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java @@ -23,12 +23,15 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.animation.Animator; import android.animation.ValueAnimator; +import android.content.Context; import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.os.Debug; import android.util.ArrayMap; import android.util.Slog; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.WindowManagerInternal; @@ -51,6 +54,8 @@ public class BoundsAnimationController { ? "BoundsAnimationController" : TAG_WM; private static final int DEBUG_ANIMATION_SLOW_DOWN_FACTOR = 1; + private static final int DEFAULT_TRANSITION_DURATION = 425; + // Only accessed on UI thread. private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>(); @@ -85,12 +90,15 @@ public class BoundsAnimationController { private final Handler mHandler; private final AppTransition mAppTransition; private final AppTransitionNotifier mAppTransitionNotifier = new AppTransitionNotifier(); + private final Interpolator mFastOutSlowInInterpolator; private boolean mFinishAnimationAfterTransition = false; - BoundsAnimationController(AppTransition transition, Handler handler) { + BoundsAnimationController(Context context, AppTransition transition, Handler handler) { mHandler = handler; mAppTransition = transition; mAppTransition.registerListenerLocked(mAppTransitionNotifier); + mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + com.android.internal.R.interpolator.fast_out_slow_in); } private final class BoundsAnimator extends ValueAnimator @@ -297,8 +305,8 @@ public class BoundsAnimationController { mRunningAnimations.put(target, animator); animator.setFloatValues(0f, 1f); animator.setDuration((animationDuration != -1 ? animationDuration - : DEFAULT_APP_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR); - animator.setInterpolator(new LinearInterpolator()); + : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR); + animator.setInterpolator(mFastOutSlowInInterpolator); animator.start(); } } diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 2a20a70b6ee9..1d50d0d3826f 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -94,13 +94,16 @@ class PinnedStackController { // The size and position information that describes where the pinned stack will go by default. private int mDefaultStackGravity; - private Size mDefaultStackSize; + private float mDefaultAspectRatio; private Point mScreenEdgeInsets; // The aspect ratio bounds of the PIP. private float mMinAspectRatio; private float mMaxAspectRatio; + // The minimum edge size of the normal PiP bounds. + private int mMinSize; + // Temp vars for calculation private final DisplayMetrics mTmpMetrics = new DisplayMetrics(); private final Rect mTmpInsets = new Rect(); @@ -151,15 +154,15 @@ class PinnedStackController { */ void reloadResources() { final Resources res = mService.mContext.getResources(); - final Size defaultSizeDp = Size.parseSize(res.getString( - com.android.internal.R.string.config_defaultPictureInPictureSize)); + mMinSize = res.getDimensionPixelSize( + com.android.internal.R.dimen.default_minimal_size_pip_resizable_task); + mDefaultAspectRatio = res.getFloat( + com.android.internal.R.dimen.config_pictureInPictureDefaultAspectRatio); final Size screenEdgeInsetsDp = Size.parseSize(res.getString( com.android.internal.R.string.config_defaultPictureInPictureScreenEdgeInsets)); mDefaultStackGravity = res.getInteger( com.android.internal.R.integer.config_defaultPictureInPictureGravity); mDisplayContent.getDisplay().getRealMetrics(mTmpMetrics); - mDefaultStackSize = new Size(dpToPx(defaultSizeDp.getWidth(), mTmpMetrics), - dpToPx(defaultSizeDp.getHeight(), mTmpMetrics)); mScreenEdgeInsets = new Point(dpToPx(screenEdgeInsetsDp.getWidth(), mTmpMetrics), dpToPx(screenEdgeInsetsDp.getHeight(), mTmpMetrics)); mMinAspectRatio = res.getFloat( @@ -199,16 +202,13 @@ class PinnedStackController { * specified aspect ratio. */ Rect transformBoundsToAspectRatio(Rect stackBounds, float aspectRatio) { - // Save the snap fraction, calculate the aspect ratio based on the current bounds + // Save the snap fraction, calculate the aspect ratio based on screen size final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds, getMovementBounds(stackBounds)); - final float radius = PointF.length(stackBounds.width(), stackBounds.height()); - final int height = (int) Math.round(Math.sqrt((radius * radius) / - (aspectRatio * aspectRatio + 1))); - final int width = Math.round(height * aspectRatio); - final int left = (int) (stackBounds.centerX() - width / 2f); - final int top = (int) (stackBounds.centerY() - height / 2f); - stackBounds.set(left, top, left + width, top + height); + final Size size = getSize(aspectRatio); + final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f); + final int top = (int) (stackBounds.centerY() - size.getHeight() / 2f); + stackBounds.set(left, top, left + size.getWidth(), top + size.getHeight()); mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction); if (mIsMinimized) { applyMinimizedOffset(stackBounds, getMovementBounds(stackBounds)); @@ -217,6 +217,14 @@ class PinnedStackController { } /** + * @return the size of the PIP based on the given {@param aspectRatio}. + */ + Size getSize(float aspectRatio) { + return mSnapAlgorithm.getSizeForAspectRatio(aspectRatio, mMinSize, + mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); + } + + /** * @return the default bounds to show the PIP when there is no active PIP. */ Rect getDefaultBounds() { @@ -224,8 +232,9 @@ class PinnedStackController { getInsetBounds(insetBounds); final Rect defaultBounds = new Rect(); - Gravity.apply(mDefaultStackGravity, mDefaultStackSize.getWidth(), - mDefaultStackSize.getHeight(), insetBounds, 0, 0, defaultBounds); + final Size size = getSize(mDefaultAspectRatio); + Gravity.apply(mDefaultStackGravity, size.getWidth(), size.getHeight(), insetBounds, + 0, mIsImeShowing ? mImeHeight : 0, defaultBounds); return defaultBounds; } @@ -344,14 +353,21 @@ class PinnedStackController { private void notifyMovementBoundsChanged(boolean fromImeAdjustement) { if (mPinnedStackListener != null) { try { - Rect insetBounds = new Rect(); + final Rect insetBounds = new Rect(); getInsetBounds(insetBounds); - Rect normalBounds = getDefaultBounds(); + final Rect normalBounds = getDefaultBounds(); if (isValidPictureInPictureAspectRatio(mAspectRatio)) { transformBoundsToAspectRatio(normalBounds, mAspectRatio); } + final Rect animatingBounds = mTmpRect; + final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID); + if (pinnedStack != null) { + pinnedStack.getAnimatingBounds(animatingBounds); + } else { + animatingBounds.set(normalBounds); + } mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds, - fromImeAdjustement); + animatingBounds, fromImeAdjustement); } catch (RemoteException e) { Slog.e(TAG_WM, "Error delivering actions changed event.", e); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index eb3a2d15a855..66ec8f0c6176 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1026,8 +1026,8 @@ public class WindowManagerService extends IWindowManager.Stub mAppTransition = new AppTransition(context, this); mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier); - mBoundsAnimationController = - new BoundsAnimationController(mAppTransition, UiThread.getHandler()); + mBoundsAnimationController = new BoundsAnimationController(context, mAppTransition, + UiThread.getHandler()); mActivityManager = ActivityManager.getService(); mAmInternal = LocalServices.getService(ActivityManagerInternal.class); diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 25c29ee9566f..450f9b638c2e 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -15,11 +15,15 @@ */ package com.android.server.notification; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.fail; +import org.json.JSONArray; +import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,6 +51,7 @@ import android.service.notification.StatusBarNotification; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; +import android.util.ArrayMap; import android.util.Xml; import java.io.BufferedInputStream; @@ -59,12 +64,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; @@ -771,6 +778,17 @@ public class RankingHelperTest { } @Test + public void testCreateChannel_defaultChannelId() throws Exception { + try { + mHelper.createNotificationChannel(pkg2, uid2, new NotificationChannel( + NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true); + fail("Allowed to create default channel"); + } catch (IllegalArgumentException e) { + // pass + } + } + + @Test public void testCreateChannel_alreadyExists() throws Exception { long[] vibration = new long[]{100, 67, 145, 156}; NotificationChannel channel = @@ -974,9 +992,58 @@ public class RankingHelperTest { assertEquals(2, actual.size()); for (NotificationChannelGroup group : actual) { - if (Objects.equals(group.getId(),ncg.getId())) { + if (Objects.equals(group.getId(), ncg.getId())) { assertEquals(1, group.getChannels().size()); } } } + + @Test + public void testCreateChannel_updateNameResId() throws Exception { + NotificationChannel nc = new NotificationChannel("id", 1, IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(pkg, uid, nc, true); + + nc = new NotificationChannel("id", 2, IMPORTANCE_DEFAULT); + mHelper.createNotificationChannel(pkg, uid, nc, true); + + assertEquals(2, mHelper.getNotificationChannel(pkg, uid, "id", false).getNameResId()); + } + + @Test + public void testDumpChannelsJson() throws Exception { + final ApplicationInfo upgrade = new ApplicationInfo(); + upgrade.targetSdkVersion = Build.VERSION_CODES.O; + try { + when(mPm.getApplicationInfoAsUser( + anyString(), anyInt(), anyInt())).thenReturn(upgrade); + } catch (PackageManager.NameNotFoundException e) { + } + ArrayMap<String, Integer> expectedChannels = new ArrayMap<>(); + int numPackages = ThreadLocalRandom.current().nextInt(1, 5); + for (int i = 0; i < numPackages; i++) { + String pkgName = "pkg" + i; + int numChannels = ThreadLocalRandom.current().nextInt(1, 10); + for (int j = 0; j < numChannels; j++) { + mHelper.createNotificationChannel(pkgName, uid, + new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true); + } + expectedChannels.put(pkgName, numChannels); + } + + // delete the first channel of the first package + String pkg = expectedChannels.keyAt(0); + mHelper.deleteNotificationChannel("pkg" + 0, uid, "0"); + // dump should not include deleted channels + int count = expectedChannels.get(pkg); + expectedChannels.put(pkg, count - 1); + + JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter()); + assertEquals(numPackages, actual.length()); + for (int i = 0; i < numPackages; i++) { + JSONObject object = actual.getJSONObject(i); + assertTrue(expectedChannels.containsKey(object.get("packageName"))); + assertEquals(expectedChannels.get(object.get("packageName")).intValue() + 1, + object.getInt("channelCount")); + } + } } diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 5bb479fdeea5..c79090203c0b 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -878,6 +878,16 @@ public final class Call { * @param id The ID of the request. */ public void onRttRequest(Call call, int id) {} + + /** + * Invoked when the RTT session failed to initiate for some reason, including rejection + * by the remote party. + * @param call The call which the RTT initiation failure occurred on. + * @param reason One of the status codes defined in + * {@link android.telecom.Connection.RttModifyStatus}, with the exception of + * {@link android.telecom.Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}. + */ + public void onRttInitiationFailure(Call call, int reason) {} } /** @@ -920,13 +930,15 @@ public final class Call { private OutputStreamWriter mTransmitStream; private int mRttMode; private final InCallAdapter mInCallAdapter; + private final String mTelecomCallId; private char[] mReadBuffer = new char[READ_BUFFER_SIZE]; /** * @hide */ - public RttCall(InputStreamReader receiveStream, OutputStreamWriter transmitStream, - int mode, InCallAdapter inCallAdapter) { + public RttCall(String telecomCallId, InputStreamReader receiveStream, + OutputStreamWriter transmitStream, int mode, InCallAdapter inCallAdapter) { + mTelecomCallId = telecomCallId; mReceiveStream = receiveStream; mTransmitStream = transmitStream; mRttMode = mode; @@ -949,7 +961,7 @@ public final class Call { * {@link #RTT_MODE_VCO}, or {@link #RTT_MODE_HCO}. */ public void setRttMode(@RttAudioMode int mode) { - mInCallAdapter.setRttMode(mode); + mInCallAdapter.setRttMode(mTelecomCallId, mode); } /** @@ -1014,6 +1026,7 @@ public final class Call { private int mState; private List<String> mCannedTextResponses = null; private String mCallingPackage; + private int mTargetSdkVersion; private String mRemainingPostDialSequence; private VideoCallImpl mVideoCallImpl; private RttCall mRttCall; @@ -1220,7 +1233,7 @@ public final class Call { * {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback. */ public void sendRttRequest() { - mInCallAdapter.sendRttRequest(); + mInCallAdapter.sendRttRequest(mTelecomCallId); } /** @@ -1231,7 +1244,7 @@ public final class Call { * @param accept {@code true} if the RTT request should be accepted, {@code false} otherwise. */ public void respondToRttRequest(int id, boolean accept) { - mInCallAdapter.respondToRttRequest(id, accept); + mInCallAdapter.respondToRttRequest(mTelecomCallId, id, accept); } /** @@ -1239,7 +1252,7 @@ public final class Call { * the {@link Callback#onRttStatusChanged(Call, boolean, RttCall)} callback. */ public void stopRtt() { - mInCallAdapter.stopRtt(); + mInCallAdapter.stopRtt(mTelecomCallId); } /** @@ -1547,22 +1560,25 @@ public final class Call { } /** {@hide} */ - Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, String callingPackage) { + Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, String callingPackage, + int targetSdkVersion) { mPhone = phone; mTelecomCallId = telecomCallId; mInCallAdapter = inCallAdapter; mState = STATE_NEW; mCallingPackage = callingPackage; + mTargetSdkVersion = targetSdkVersion; } /** {@hide} */ Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, int state, - String callingPackage) { + String callingPackage, int targetSdkVersion) { mPhone = phone; mTelecomCallId = telecomCallId; mInCallAdapter = inCallAdapter; mState = state; mCallingPackage = callingPackage; + mTargetSdkVersion = targetSdkVersion; } /** {@hide} */ @@ -1588,7 +1604,8 @@ public final class Call { cannedTextResponsesChanged = true; } - VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl(mCallingPackage); + VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl(mCallingPackage, + mTargetSdkVersion); boolean videoCallChanged = parcelableCall.isVideoCallProviderChanged() && !Objects.equals(mVideoCallImpl, newVideoCallImpl); if (videoCallChanged) { @@ -1644,7 +1661,7 @@ public final class Call { new ParcelFileDescriptor.AutoCloseOutputStream( parcelableRttCall.getTransmitStream()), StandardCharsets.UTF_8); - RttCall newRttCall = new Call.RttCall( + RttCall newRttCall = new Call.RttCall(mTelecomCallId, receiveStream, transmitStream, parcelableRttCall.getRttMode(), mInCallAdapter); if (mRttCall == null) { isRttChanged = true; @@ -1724,6 +1741,15 @@ public final class Call { } } + /** @hide */ + final void internalOnRttInitiationFailure(int reason) { + for (CallbackRecord<Callback> record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(() -> callback.onRttInitiationFailure(call, reason)); + } + } + private void fireStateChanged(final int newState) { for (CallbackRecord<Callback> record : mCallbackRecords) { final Call call = this; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 3e690b997b29..833affa379f3 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -20,9 +20,12 @@ import com.android.internal.os.SomeArgs; import com.android.internal.telecom.IVideoCallback; import com.android.internal.telecom.IVideoProvider; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.Notification; +import android.content.Intent; import android.hardware.camera2.CameraManager; import android.net.Uri; import android.os.Binder; @@ -39,6 +42,8 @@ import android.view.Surface; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -764,6 +769,10 @@ public abstract class Connection extends Conferenceable { /** @hide */ public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {} public void onAudioRouteChanged(Connection c, int audioRoute) {} + public void onRttInitiationSuccess(Connection c) {} + public void onRttInitiationFailure(Connection c, int reason) {} + public void onRttSessionRemotelyTerminated(Connection c) {} + public void onRemoteRttRequest(Connection c) {} } /** @@ -774,12 +783,16 @@ public abstract class Connection extends Conferenceable { private static final int READ_BUFFER_SIZE = 1000; private final InputStreamReader mPipeFromInCall; private final OutputStreamWriter mPipeToInCall; + private final ParcelFileDescriptor mFdFromInCall; + private final ParcelFileDescriptor mFdToInCall; private char[] mReadBuffer = new char[READ_BUFFER_SIZE]; /** * @hide */ public RttTextStream(ParcelFileDescriptor toInCall, ParcelFileDescriptor fromInCall) { + mFdFromInCall = fromInCall; + mFdToInCall = toInCall; mPipeFromInCall = new InputStreamReader( new ParcelFileDescriptor.AutoCloseInputStream(fromInCall)); mPipeToInCall = new OutputStreamWriter( @@ -823,6 +836,47 @@ public abstract class Connection extends Conferenceable { return null; } } + + /** @hide */ + public ParcelFileDescriptor getFdFromInCall() { + return mFdFromInCall; + } + + /** @hide */ + public ParcelFileDescriptor getFdToInCall() { + return mFdToInCall; + } + } + + /** + * Provides constants to represent the results of responses to session modify requests sent via + * {@link Call#sendRttRequest()} + */ + public static final class RttModifyStatus { + /** + * Session modify request was successful. + */ + public static final int SESSION_MODIFY_REQUEST_SUCCESS = 1; + + /** + * Session modify request failed. + */ + public static final int SESSION_MODIFY_REQUEST_FAIL = 2; + + /** + * Session modify request ignored due to invalid parameters. + */ + public static final int SESSION_MODIFY_REQUEST_INVALID = 3; + + /** + * Session modify request timed out. + */ + public static final int SESSION_MODIFY_REQUEST_TIMED_OUT = 4; + + /** + * Session modify request rejected by remote user. + */ + public static final int SESSION_MODIFY_REQUEST_REJECTED_BY_REMOTE = 5; } /** @@ -1005,7 +1059,7 @@ public abstract class Connection extends Conferenceable { try { onSetCamera((String) args.arg1); onSetCamera((String) args.arg1, (String) args.arg2, args.argi1, - args.argi2); + args.argi2, args.argi3); } finally { args.recycle(); } @@ -1065,7 +1119,9 @@ public abstract class Connection extends Conferenceable { MSG_REMOVE_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget(); } - public void setCamera(String cameraId, String callingPackageName) { + public void setCamera(String cameraId, String callingPackageName, + int targetSdkVersion) { + SomeArgs args = SomeArgs.obtain(); args.arg1 = cameraId; // Propagate the calling package; originally determined in @@ -1077,6 +1133,9 @@ public abstract class Connection extends Conferenceable { // check to see if the calling app is able to use the camera. args.argi1 = Binder.getCallingUid(); args.argi2 = Binder.getCallingPid(); + // Pass along the target SDK version of the calling InCallService. This is used to + // maintain backwards compatibility of the API for older callers. + args.argi3 = targetSdkVersion; mMessageHandler.obtainMessage(MSG_SET_CAMERA, args).sendToTarget(); } @@ -1179,10 +1238,11 @@ public abstract class Connection extends Conferenceable { * @param callingPackageName The AppOpps package name of the caller. * @param callingUid The UID of the caller. * @param callingPid The PID of the caller. + * @param targetSdkVersion The target SDK version of the caller. * @hide */ public void onSetCamera(String cameraId, String callingPackageName, int callingUid, - int callingPid) {} + int callingPid, int targetSdkVersion) {} /** * Sets the surface to be used for displaying a preview of what the user's camera is @@ -2426,6 +2486,47 @@ public abstract class Connection extends Conferenceable { } /** + * Informs listeners that a previously requested RTT session via + * {@link ConnectionRequest#isRequestingRtt()} or + * {@link #onStartRtt(ParcelFileDescriptor, ParcelFileDescriptor)} has succeeded. + * @hide + */ + public final void sendRttInitiationSuccess() { + mListeners.forEach((l) -> l.onRttInitiationSuccess(Connection.this)); + } + + /** + * Informs listeners that a previously requested RTT session via + * {@link ConnectionRequest#isRequestingRtt()} or + * {@link #onStartRtt(ParcelFileDescriptor, ParcelFileDescriptor)} + * has failed. + * @param reason One of the reason codes defined in {@link RttModifyStatus}, with the + * exception of {@link RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}. + * @hide + */ + public final void sendRttInitiationFailure(int reason) { + mListeners.forEach((l) -> l.onRttInitiationFailure(Connection.this, reason)); + } + + /** + * Informs listeners that a currently active RTT session has been terminated by the remote + * side of the coll. + * @hide + */ + public final void sendRttSessionRemotelyTerminated() { + mListeners.forEach((l) -> l.onRttSessionRemotelyTerminated(Connection.this)); + } + + /** + * Informs listeners that the remote side of the call has requested an upgrade to include an + * RTT session in the call. + * @hide + */ + public final void sendRemoteRttRequest() { + mListeners.forEach((l) -> l.onRemoteRttRequest(Connection.this)); + } + + /** * Notifies this Connection that the {@link #getAudioState()} property has a new value. * * @param state The new connection audio state. @@ -2592,9 +2693,73 @@ public abstract class Connection extends Conferenceable { * regular {@link ConnectionService}, the Telecom framework will display its own incoming call * user interface to allow the user to choose whether to answer the new incoming call and * disconnect other ongoing calls, or to reject the new incoming call. + * <p> + * You should trigger the display of the incoming call user interface for your application by + * showing a {@link Notification} with a full-screen {@link Intent} specified. + * For example: + * <pre><code> + * // Create an intent which triggers your fullscreen incoming call user interface. + * Intent intent = new Intent(Intent.ACTION_MAIN, null); + * intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); + * intent.setClass(context, YourIncomingCallActivity.class); + * PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, 0); + * + * // Build the notification as an ongoing high priority item; this ensures it will show as + * // a heads up notification which slides down over top of the current content. + * final Notification.Builder builder = new Notification.Builder(context); + * builder.setOngoing(true); + * builder.setPriority(Notification.PRIORITY_HIGH); + * + * // Set notification content intent to take user to fullscreen UI if user taps on the + * // notification body. + * builder.setContentIntent(pendingIntent); + * // Set full screen intent to trigger display of the fullscreen UI when the notification + * // manager deems it appropriate. + * builder.setFullScreenIntent(pendingIntent, true); + * + * // Setup notification content. + * builder.setSmallIcon( yourIconResourceId ); + * builder.setContentTitle("Your notification title"); + * builder.setContentText("Your notification content."); + * + * // Use builder.addAction(..) to add buttons to answer or reject the call. + * + * NotificationManager notificationManager = mContext.getSystemService( + * NotificationManager.class); + * notificationManager.notify(YOUR_TAG, YOUR_ID, builder.build()); + * </code></pre> */ public void onShowIncomingCallUi() {} + /** + * Notifies this {@link Connection} that the user has requested an RTT session. + * The connection service should call {@link #sendRttInitiationSuccess} or + * {@link #sendRttInitiationFailure} to inform Telecom of the success or failure of the + * request, respectively. + * @param rttTextStream The object that should be used to send text to or receive text from + * the in-call app. + * @hide + */ + public void onStartRtt(@NonNull RttTextStream rttTextStream) {} + + /** + * Notifies this {@link Connection} that it should terminate any existing RTT communication + * channel. No response to Telecom is needed for this method. + * @hide + */ + public void onStopRtt() {} + + /** + * Notifies this connection of a response to a previous remotely-initiated RTT upgrade + * request sent via {@link #sendRemoteRttRequest}. Acceptance of the request is + * indicated by the supplied {@link RttTextStream} being non-null, and rejection is + * indicated by {@code rttTextStream} being {@code null} + * @hide + * @param rttTextStream The object that should be used to send text to or receive text from + * the in-call app. + */ + public void handleRttUpgradeResponse(@Nullable RttTextStream rttTextStream) {} + static String toLogSafePhoneNumber(String number) { // For unknown number, log empty string. if (number == null) { diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 6e100298da35..bf8f8e4e723e 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -26,6 +26,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.telecom.Logging.Session; import com.android.internal.os.SomeArgs; @@ -119,6 +121,9 @@ public abstract class ConnectionService extends Service { private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC"; private static final String SESSION_SEND_CALL_EVENT = "CS.sCE"; private static final String SESSION_EXTRAS_CHANGED = "CS.oEC"; + private static final String SESSION_START_RTT = "CS.+RTT"; + private static final String SESSION_STOP_RTT = "CS.-RTT"; + private static final String SESSION_RTT_UPGRADE_RESPONSE = "CS.rTRUR"; private static final int MSG_ADD_CONNECTION_SERVICE_ADAPTER = 1; private static final int MSG_CREATE_CONNECTION = 2; @@ -144,6 +149,9 @@ public abstract class ConnectionService extends Service { private static final int MSG_SEND_CALL_EVENT = 23; private static final int MSG_ON_EXTRAS_CHANGED = 24; private static final int MSG_CREATE_CONNECTION_FAILED = 25; + private static final int MSG_ON_START_RTT = 26; + private static final int MSG_ON_STOP_RTT = 27; + private static final int MSG_RTT_UPGRADE_RESPONSE = 28; private static Connection sNullConnection; @@ -214,6 +222,7 @@ public abstract class ConnectionService extends Service { @Override public void createConnectionFailed( + PhoneAccountHandle connectionManagerPhoneAccount, String callId, ConnectionRequest request, boolean isIncoming, @@ -224,6 +233,7 @@ public abstract class ConnectionService extends Service { args.arg1 = callId; args.arg2 = request; args.arg3 = Log.createSubsession(); + args.arg4 = connectionManagerPhoneAccount; args.argi1 = isIncoming ? 1 : 0; mHandler.obtainMessage(MSG_CREATE_CONNECTION_FAILED, args).sendToTarget(); } finally { @@ -501,6 +511,53 @@ public abstract class ConnectionService extends Service { Log.endSession(); } } + + @Override + public void startRtt(String callId, ParcelFileDescriptor fromInCall, + ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException { + Log.startSession(sessionInfo, SESSION_START_RTT); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = new Connection.RttTextStream(toInCall, fromInCall); + args.arg3 = Log.createSubsession(); + mHandler.obtainMessage(MSG_ON_START_RTT, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override + public void stopRtt(String callId, Session.Info sessionInfo) throws RemoteException { + Log.startSession(sessionInfo, SESSION_STOP_RTT); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = Log.createSubsession(); + mHandler.obtainMessage(MSG_ON_STOP_RTT, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + + @Override + public void respondToRttUpgradeRequest(String callId, ParcelFileDescriptor fromInCall, + ParcelFileDescriptor toInCall, Session.Info sessionInfo) throws RemoteException { + Log.startSession(sessionInfo, SESSION_RTT_UPGRADE_RESPONSE); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + if (toInCall == null || fromInCall == null) { + args.arg2 = null; + } else { + args.arg2 = new Connection.RttTextStream(toInCall, fromInCall); + } + args.arg3 = Log.createSubsession(); + mHandler.obtainMessage(MSG_RTT_UPGRADE_RESPONSE, args).sendToTarget(); + } finally { + Log.endSession(); + } + } }; private final Handler mHandler = new Handler(Looper.getMainLooper()) { @@ -581,6 +638,8 @@ public abstract class ConnectionService extends Service { final String id = (String) args.arg1; final ConnectionRequest request = (ConnectionRequest) args.arg2; final boolean isIncoming = args.argi1 == 1; + final PhoneAccountHandle connectionMgrPhoneAccount = + (PhoneAccountHandle) args.arg4; if (!mAreAccountsInitialized) { Log.d(this, "Enqueueing pre-init request %s", id); mPreInitializationConnectionRequests.add( @@ -589,12 +648,14 @@ public abstract class ConnectionService extends Service { null /*lock*/) { @Override public void loggedRun() { - createConnectionFailed(id, request, isIncoming); + createConnectionFailed(connectionMgrPhoneAccount, id, + request, isIncoming); } }.prepare()); } else { Log.i(this, "createConnectionFailed %s", id); - createConnectionFailed(id, request, isIncoming); + createConnectionFailed(connectionMgrPhoneAccount, id, request, + isIncoming); } } finally { args.recycle(); @@ -848,6 +909,49 @@ public abstract class ConnectionService extends Service { } break; } + case MSG_ON_START_RTT: { + SomeArgs args = (SomeArgs) msg.obj; + try { + Log.continueSession((Session) args.arg3, + SESSION_HANDLER + SESSION_START_RTT); + String callId = (String) args.arg1; + Connection.RttTextStream rttTextStream = + (Connection.RttTextStream) args.arg2; + startRtt(callId, rttTextStream); + } finally { + args.recycle(); + Log.endSession(); + } + break; + } + case MSG_ON_STOP_RTT: { + SomeArgs args = (SomeArgs) msg.obj; + try { + Log.continueSession((Session) args.arg2, + SESSION_HANDLER + SESSION_STOP_RTT); + String callId = (String) args.arg1; + stopRtt(callId); + } finally { + args.recycle(); + Log.endSession(); + } + break; + } + case MSG_RTT_UPGRADE_RESPONSE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + Log.continueSession((Session) args.arg3, + SESSION_HANDLER + SESSION_RTT_UPGRADE_RESPONSE); + String callId = (String) args.arg1; + Connection.RttTextStream rttTextStream = + (Connection.RttTextStream) args.arg2; + handleRttUpgradeResponse(callId, rttTextStream); + } finally { + args.recycle(); + Log.endSession(); + } + break; + } default: break; } @@ -1136,6 +1240,38 @@ public abstract class ConnectionService extends Service { mAdapter.setAudioRoute(id, audioRoute); } } + + @Override + public void onRttInitiationSuccess(Connection c) { + String id = mIdByConnection.get(c); + if (id != null) { + mAdapter.onRttInitiationSuccess(id); + } + } + + @Override + public void onRttInitiationFailure(Connection c, int reason) { + String id = mIdByConnection.get(c); + if (id != null) { + mAdapter.onRttInitiationFailure(id, reason); + } + } + + @Override + public void onRttSessionRemotelyTerminated(Connection c) { + String id = mIdByConnection.get(c); + if (id != null) { + mAdapter.onRttSessionRemotelyTerminated(id); + } + } + + @Override + public void onRemoteRttRequest(Connection c) { + String id = mIdByConnection.get(c); + if (id != null) { + mAdapter.onRemoteRttRequest(id); + } + } }; /** {@inheritDoc} */ @@ -1225,14 +1361,15 @@ public abstract class ConnectionService extends Service { } } - private void createConnectionFailed(final String callId, final ConnectionRequest request, - boolean isIncoming) { + private void createConnectionFailed(final PhoneAccountHandle callManagerAccount, + final String callId, final ConnectionRequest request, + boolean isIncoming) { Log.i(this, "createConnectionFailed %s", callId); if (isIncoming) { - onCreateIncomingConnectionFailed(request); + onCreateIncomingConnectionFailed(callManagerAccount, request); } else { - onCreateOutgoingConnectionFailed(request); + onCreateOutgoingConnectionFailed(callManagerAccount, request); } } @@ -1430,7 +1567,6 @@ public abstract class ConnectionService extends Service { if (connection != null) { connection.onCallEvent(event, extras); } - } /** @@ -1454,6 +1590,34 @@ public abstract class ConnectionService extends Service { } } + private void startRtt(String callId, Connection.RttTextStream rttTextStream) { + Log.d(this, "startRtt(%s)", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "startRtt").onStartRtt(rttTextStream); + } else if (mConferenceById.containsKey(callId)) { + Log.w(this, "startRtt called on a conference."); + } + } + + private void stopRtt(String callId) { + Log.d(this, "stopRtt(%s)", callId); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "stopRtt").onStopRtt(); + } else if (mConferenceById.containsKey(callId)) { + Log.w(this, "stopRtt called on a conference."); + } + } + + private void handleRttUpgradeResponse(String callId, Connection.RttTextStream rttTextStream) { + Log.d(this, "handleRttUpgradeResponse(%s, %s)", callId, rttTextStream == null); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "handleRttUpgradeResponse") + .handleRttUpgradeResponse(rttTextStream); + } else if (mConferenceById.containsKey(callId)) { + Log.w(this, "handleRttUpgradeResponse called on a conference."); + } + } + private void onPostDialContinue(String callId, boolean proceed) { Log.d(this, "onPostDialContinue(%s)", callId); findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed); @@ -1682,9 +1846,12 @@ public abstract class ConnectionService extends Service { * <p> * See {@link TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)} for more information. * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. * @param request The incoming connection request. */ - public void onCreateIncomingConnectionFailed(ConnectionRequest request) { + public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { } /** @@ -1698,9 +1865,12 @@ public abstract class ConnectionService extends Service { * <p> * See {@link TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)} for more information. * + * @param connectionManagerPhoneAccount See description at + * {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}. * @param request The outgoing connection request. */ - public void onCreateOutgoingConnectionFailed(ConnectionRequest request) { + public void onCreateOutgoingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, + ConnectionRequest request) { } /** diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java index 9542b73c68fb..63bdf74d383c 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java @@ -547,4 +547,66 @@ final class ConnectionServiceAdapter implements DeathRecipient { } } } + + /** + * Notifies Telecom that an RTT session was successfully established. + * + * @param callId The unique ID of the call. + */ + void onRttInitiationSuccess(String callId) { + Log.v(this, "onRttInitiationSuccess: %s", callId); + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.onRttInitiationSuccess(callId, Log.getExternalSession()); + } catch (RemoteException ignored) { + } + } + } + + /** + * Notifies Telecom that a requested RTT session failed to be established. + * + * @param callId The unique ID of the call. + */ + void onRttInitiationFailure(String callId, int reason) { + Log.v(this, "onRttInitiationFailure: %s", callId); + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.onRttInitiationFailure(callId, reason, Log.getExternalSession()); + } catch (RemoteException ignored) { + } + } + } + + /** + * Notifies Telecom that an established RTT session was terminated by the remote user on + * the call. + * + * @param callId The unique ID of the call. + */ + void onRttSessionRemotelyTerminated(String callId) { + Log.v(this, "onRttSessionRemotelyTerminated: %s", callId); + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.onRttSessionRemotelyTerminated(callId, Log.getExternalSession()); + } catch (RemoteException ignored) { + } + } + } + + /** + * Notifies Telecom that the remote user on the call has requested an upgrade to an RTT + * session for this call. + * + * @param callId The unique ID of the call. + */ + void onRemoteRttRequest(String callId) { + Log.v(this, "onRemoteRttRequest: %s", callId); + for (IConnectionServiceAdapter adapter : mAdapters) { + try { + adapter.onRemoteRttRequest(callId, Log.getExternalSession()); + } catch (RemoteException ignored) { + } + } + } } diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java index cc437f9c7cd0..80e3c33a443d 100644 --- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java +++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java @@ -68,6 +68,10 @@ final class ConnectionServiceAdapterServant { private static final int MSG_SET_CONNECTION_PROPERTIES = 27; private static final int MSG_SET_PULLING = 28; private static final int MSG_SET_AUDIO_ROUTE = 29; + private static final int MSG_ON_RTT_INITIATION_SUCCESS = 30; + private static final int MSG_ON_RTT_INITIATION_FAILURE = 31; + private static final int MSG_ON_RTT_REMOTELY_TERMINATED = 32; + private static final int MSG_ON_RTT_UPGRADE_REQUEST = 33; private final IConnectionServiceAdapter mDelegate; @@ -300,6 +304,20 @@ final class ConnectionServiceAdapterServant { } break; } + case MSG_ON_RTT_INITIATION_SUCCESS: + mDelegate.onRttInitiationSuccess((String) msg.obj, null /*Session.Info*/); + break; + case MSG_ON_RTT_INITIATION_FAILURE: + mDelegate.onRttInitiationFailure((String) msg.obj, msg.arg1, + null /*Session.Info*/); + break; + case MSG_ON_RTT_REMOTELY_TERMINATED: + mDelegate.onRttSessionRemotelyTerminated((String) msg.obj, + null /*Session.Info*/); + break; + case MSG_ON_RTT_UPGRADE_REQUEST: + mDelegate.onRemoteRttRequest((String) msg.obj, null /*Session.Info*/); + break; } } }; @@ -537,6 +555,32 @@ final class ConnectionServiceAdapterServant { args.arg3 = extras; mHandler.obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget(); } + + @Override + public void onRttInitiationSuccess(String connectionId, Session.Info sessionInfo) + throws RemoteException { + mHandler.obtainMessage(MSG_ON_RTT_INITIATION_SUCCESS, connectionId).sendToTarget(); + } + + @Override + public void onRttInitiationFailure(String connectionId, int reason, + Session.Info sessionInfo) + throws RemoteException { + mHandler.obtainMessage(MSG_ON_RTT_INITIATION_FAILURE, reason, 0, connectionId) + .sendToTarget(); + } + + @Override + public void onRttSessionRemotelyTerminated(String connectionId, Session.Info sessionInfo) + throws RemoteException { + mHandler.obtainMessage(MSG_ON_RTT_REMOTELY_TERMINATED, connectionId).sendToTarget(); + } + + @Override + public void onRemoteRttRequest(String connectionId, Session.Info sessionInfo) + throws RemoteException { + mHandler.obtainMessage(MSG_ON_RTT_UPGRADE_REQUEST, connectionId).sendToTarget(); + } }; public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) { diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java index d640b1dd6022..9559a28c0bef 100644 --- a/telecomm/java/android/telecom/InCallAdapter.java +++ b/telecomm/java/android/telecom/InCallAdapter.java @@ -379,9 +379,9 @@ public final class InCallAdapter { /** * Sends an RTT upgrade request to the remote end of the connection. */ - public void sendRttRequest() { + public void sendRttRequest(String callId) { try { - mAdapter.sendRttRequest(); + mAdapter.sendRttRequest(callId); } catch (RemoteException ignored) { } } @@ -392,9 +392,9 @@ public final class InCallAdapter { * @param id the ID of the request as specified by Telecom * @param accept Whether the request should be accepted. */ - public void respondToRttRequest(int id, boolean accept) { + public void respondToRttRequest(String callId, int id, boolean accept) { try { - mAdapter.respondToRttRequest(id, accept); + mAdapter.respondToRttRequest(callId, id, accept); } catch (RemoteException ignored) { } } @@ -402,9 +402,9 @@ public final class InCallAdapter { /** * Instructs Telecom to shut down the RTT communication channel. */ - public void stopRtt() { + public void stopRtt(String callId) { try { - mAdapter.stopRtt(); + mAdapter.stopRtt(callId); } catch (RemoteException ignored) { } } @@ -413,9 +413,9 @@ public final class InCallAdapter { * Sets the RTT audio mode. * @param mode the desired RTT audio mode */ - public void setRttMode(int mode) { + public void setRttMode(String callId, int mode) { try { - mAdapter.setRttMode(mode); + mAdapter.setRttMode(callId, mode); } catch (RemoteException ignored) { } } diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 4bc64c05bfee..e384d4696ab0 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -77,6 +77,7 @@ public abstract class InCallService extends Service { private static final int MSG_SILENCE_RINGER = 8; private static final int MSG_ON_CONNECTION_EVENT = 9; private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10; + private static final int MSG_ON_RTT_INITIATION_FAILURE = 11; /** Default Handler used to consolidate binder method calls onto a single thread. */ private final Handler mHandler = new Handler(Looper.getMainLooper()) { @@ -89,7 +90,8 @@ public abstract class InCallService extends Service { switch (msg.what) { case MSG_SET_IN_CALL_ADAPTER: String callingPackage = getApplicationContext().getOpPackageName(); - mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage); + mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage, + getApplicationContext().getApplicationInfo().targetSdkVersion); mPhone.addListener(mPhoneListener); onPhoneCreated(mPhone); break; @@ -140,6 +142,12 @@ public abstract class InCallService extends Service { mPhone.internalOnRttUpgradeRequest(callId, requestId); break; } + case MSG_ON_RTT_INITIATION_FAILURE: { + String callId = (String) msg.obj; + int reason = msg.arg1; + mPhone.internalOnRttInitiationFailure(callId, reason); + break; + } default: break; } @@ -210,6 +218,11 @@ public abstract class InCallService extends Service { public void onRttUpgradeRequest(String callId, int id) { mHandler.obtainMessage(MSG_ON_RTT_UPGRADE_REQUEST, id, 0, callId).sendToTarget(); } + + @Override + public void onRttInitiationFailure(String callId, int reason) { + mHandler.obtainMessage(MSG_ON_RTT_INITIATION_FAILURE, reason, 0, callId).sendToTarget(); + } } private Phone.Listener mPhoneListener = new Phone.Listener() { diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java index 975aa5a332ca..85a92d1a135a 100644 --- a/telecomm/java/android/telecom/ParcelableCall.java +++ b/telecomm/java/android/telecom/ParcelableCall.java @@ -193,13 +193,16 @@ public final class ParcelableCall implements Parcelable { /** * Returns an object for remotely communicating through the video call provider's binder. - + * + * @param callingPackageName the package name of the calling InCallService. + * @param targetSdkVersion the target SDK version of the calling InCallService. * @return The video call. */ - public VideoCallImpl getVideoCallImpl(String callingPackageName) { + public VideoCallImpl getVideoCallImpl(String callingPackageName, int targetSdkVersion) { if (mVideoCall == null && mVideoCallProvider != null) { try { - mVideoCall = new VideoCallImpl(mVideoCallProvider, callingPackageName); + mVideoCall = new VideoCallImpl(mVideoCallProvider, callingPackageName, + targetSdkVersion); } catch (RemoteException ignored) { // Ignore RemoteException. } diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index ebd04c7cd666..066f6c26dd52 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -127,14 +127,20 @@ public final class Phone { private final String mCallingPackage; - Phone(InCallAdapter adapter, String callingPackage) { + /** + * The Target SDK version of the InCallService implementation. + */ + private final int mTargetSdkVersion; + + Phone(InCallAdapter adapter, String callingPackage, int targetSdkVersion) { mInCallAdapter = adapter; mCallingPackage = callingPackage; + mTargetSdkVersion = targetSdkVersion; } final void internalAddCall(ParcelableCall parcelableCall) { Call call = new Call(this, parcelableCall.getId(), mInCallAdapter, - parcelableCall.getState(), mCallingPackage); + parcelableCall.getState(), mCallingPackage, mTargetSdkVersion); mCallByTelecomCallId.put(parcelableCall.getId(), call); mCalls.add(call); checkCallTree(parcelableCall); @@ -208,6 +214,13 @@ public final class Phone { } } + final void internalOnRttInitiationFailure(String callId, int reason) { + Call call = mCallByTelecomCallId.get(callId); + if (call != null) { + call.internalOnRttInitiationFailure(reason); + } + } + /** * Called to destroy the phone and cleanup any lingering calls. */ diff --git a/telecomm/java/android/telecom/RemoteConnection.java b/telecomm/java/android/telecom/RemoteConnection.java index 77e0e5486127..57fc9ced91ae 100644 --- a/telecomm/java/android/telecom/RemoteConnection.java +++ b/telecomm/java/android/telecom/RemoteConnection.java @@ -20,6 +20,7 @@ import com.android.internal.telecom.IConnectionService; import com.android.internal.telecom.IVideoCallback; import com.android.internal.telecom.IVideoProvider; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.hardware.camera2.CameraManager; @@ -231,6 +232,41 @@ public final class RemoteConnection { * @param extras Extras associated with the event. */ public void onConnectionEvent(RemoteConnection connection, String event, Bundle extras) {} + + /** + * Indicates that a RTT session was successfully established on this + * {@link RemoteConnection}. See {@link Connection#sendRttInitiationSuccess()}. + * @hide + * @param connection The {@code RemoteConnection} invoking this method. + */ + public void onRttInitiationSuccess(RemoteConnection connection) {} + + /** + * Indicates that a RTT session failed to be established on this + * {@link RemoteConnection}. See {@link Connection#sendRttInitiationFailure()}. + * @hide + * @param connection The {@code RemoteConnection} invoking this method. + * @param reason One of the reason codes defined in {@link Connection.RttModifyStatus}, + * with the exception of + * {@link Connection.RttModifyStatus#SESSION_MODIFY_REQUEST_SUCCESS}. + */ + public void onRttInitiationFailure(RemoteConnection connection, int reason) {} + + /** + * Indicates that an established RTT session was terminated remotely on this + * {@link RemoteConnection}. See {@link Connection#sendRttSessionRemotelyTerminated()} + * @hide + * @param connection The {@code RemoteConnection} invoking this method. + */ + public void onRttSessionRemotelyTerminated(RemoteConnection connection) {} + + /** + * Indicates that the remote user on this {@link RemoteConnection} has requested an upgrade + * to an RTT session. See {@link Connection#sendRemoteRttRequest()} + * @hide + * @param connection The {@code RemoteConnection} invoking this method. + */ + public void onRemoteRttRequest(RemoteConnection connection) {} } /** @@ -410,6 +446,8 @@ public final class RemoteConnection { private final String mCallingPackage; + private final int mTargetSdkVersion; + /** * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is * load factor before resizing, 1 means we only expect a single thread to @@ -418,9 +456,12 @@ public final class RemoteConnection { private final Set<Callback> mCallbacks = Collections.newSetFromMap( new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1)); - VideoProvider(IVideoProvider videoProviderBinder, String callingPackage) { + VideoProvider(IVideoProvider videoProviderBinder, String callingPackage, + int targetSdkVersion) { + mVideoProviderBinder = videoProviderBinder; mCallingPackage = callingPackage; + mTargetSdkVersion = targetSdkVersion; try { mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder()); } catch (RemoteException e) { @@ -455,7 +496,7 @@ public final class RemoteConnection { */ public void setCamera(String cameraId) { try { - mVideoProviderBinder.setCamera(cameraId, mCallingPackage); + mVideoProviderBinder.setCamera(cameraId, mCallingPackage, mTargetSdkVersion); } catch (RemoteException e) { } } @@ -631,7 +672,7 @@ public final class RemoteConnection { * @hide */ RemoteConnection(String callId, IConnectionService connectionService, - ParcelableConnection connection, String callingPackage) { + ParcelableConnection connection, String callingPackage, int targetSdkVersion) { mConnectionId = callId; mConnectionService = connectionService; mConnected = true; @@ -643,7 +684,8 @@ public final class RemoteConnection { mVideoState = connection.getVideoState(); IVideoProvider videoProvider = connection.getVideoProvider(); if (videoProvider != null) { - mVideoProvider = new RemoteConnection.VideoProvider(videoProvider, callingPackage); + mVideoProvider = new RemoteConnection.VideoProvider(videoProvider, callingPackage, + targetSdkVersion); } else { mVideoProvider = null; } @@ -1046,6 +1088,61 @@ public final class RemoteConnection { } /** + * Notifies this {@link RemoteConnection} that the user has requested an RTT session. + * @param rttTextStream The object that should be used to send text to or receive text from + * the in-call app. + * @hide + */ + public void startRtt(@NonNull Connection.RttTextStream rttTextStream) { + try { + if (mConnected) { + mConnectionService.startRtt(mConnectionId, rttTextStream.getFdFromInCall(), + rttTextStream.getFdToInCall(), null /*Session.Info*/); + } + } catch (RemoteException ignored) { + } + } + + /** + * Notifies this {@link RemoteConnection} that it should terminate any existing RTT + * session. No response to Telecom is needed for this method. + * @hide + */ + public void stopRtt() { + try { + if (mConnected) { + mConnectionService.stopRtt(mConnectionId, null /*Session.Info*/); + } + } catch (RemoteException ignored) { + } + } + + /** + * Notifies this {@link RemoteConnection} of a response to a previous remotely-initiated RTT + * upgrade request sent via {@link Connection#sendRemoteRttRequest}. + * Acceptance of the request is indicated by the supplied {@link RttTextStream} being non-null, + * and rejection is indicated by {@code rttTextStream} being {@code null} + * @hide + * @param rttTextStream The object that should be used to send text to or receive text from + * the in-call app. + */ + public void sendRttUpgradeResponse(@Nullable Connection.RttTextStream rttTextStream) { + try { + if (mConnected) { + if (rttTextStream == null) { + mConnectionService.respondToRttUpgradeRequest(mConnectionId, + null, null, null /*Session.Info*/); + } else { + mConnectionService.respondToRttUpgradeRequest(mConnectionId, + rttTextStream.getFdFromInCall(), rttTextStream.getFdToInCall(), + null /*Session.Info*/); + } + } + } catch (RemoteException ignored) { + } + } + + /** * Obtain the {@code RemoteConnection}s with which this {@code RemoteConnection} may be * successfully asked to create a conference with. * @@ -1411,6 +1508,47 @@ public final class RemoteConnection { } } + /** @hide */ + void onRttInitiationSuccess() { + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post( + () -> callback.onRttInitiationSuccess(connection)); + } + } + + /** @hide */ + void onRttInitiationFailure(int reason) { + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post( + () -> callback.onRttInitiationFailure(connection, reason)); + } + } + + /** @hide */ + void onRttSessionRemotelyTerminated() { + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post( + () -> callback.onRttSessionRemotelyTerminated(connection)); + } + } + + /** @hide */ + void onRemoteRttRequest() { + for (CallbackRecord record : mCallbackRecords) { + final RemoteConnection connection = this; + final Callback callback = record.getCallback(); + record.getHandler().post( + () -> callback.onRemoteRttRequest(connection)); + } + } + + /** /** * Create a RemoteConnection represents a failure, and which will be in * {@link Connection#STATE_DISCONNECTED}. Attempting to use it for anything will almost diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index 60a40f5261dd..06cdd1aa7c3c 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -286,10 +286,11 @@ final class RemoteConnectionService { String callingPackage = mOurConnectionServiceImpl.getApplicationContext() .getOpPackageName(); + int targetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo().targetSdkVersion; RemoteConnection.VideoProvider remoteVideoProvider = null; if (videoProvider != null) { remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider, - callingPackage); + callingPackage, targetSdkVersion); } findConnectionForAction(callId, "setVideoProvider") .setVideoProvider(remoteVideoProvider); @@ -357,8 +358,11 @@ final class RemoteConnectionService { Session.Info sessionInfo) { String callingPackage = mOurConnectionServiceImpl.getApplicationContext(). getOpPackageName(); + int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo() + .targetSdkVersion; RemoteConnection remoteConnection = new RemoteConnection(callId, - mOutgoingConnectionServiceRpc, connection, callingPackage); + mOutgoingConnectionServiceRpc, connection, callingPackage, + callingTargetSdkVersion); mConnectionById.put(callId, remoteConnection); remoteConnection.registerCallback(new RemoteConnection.Callback() { @Override @@ -405,6 +409,50 @@ final class RemoteConnectionService { extras); } } + + @Override + public void onRttInitiationSuccess(String callId, Session.Info sessionInfo) + throws RemoteException { + if (hasConnection(callId)) { + findConnectionForAction(callId, "onRttInitiationSuccess") + .onRttInitiationSuccess(); + } else { + Log.w(this, "onRttInitiationSuccess called on a remote conference"); + } + } + + @Override + public void onRttInitiationFailure(String callId, int reason, Session.Info sessionInfo) + throws RemoteException { + if (hasConnection(callId)) { + findConnectionForAction(callId, "onRttInitiationFailure") + .onRttInitiationFailure(reason); + } else { + Log.w(this, "onRttInitiationFailure called on a remote conference"); + } + } + + @Override + public void onRttSessionRemotelyTerminated(String callId, Session.Info sessionInfo) + throws RemoteException { + if (hasConnection(callId)) { + findConnectionForAction(callId, "onRttSessionRemotelyTerminated") + .onRttSessionRemotelyTerminated(); + } else { + Log.w(this, "onRttSessionRemotelyTerminated called on a remote conference"); + } + } + + @Override + public void onRemoteRttRequest(String callId, Session.Info sessionInfo) + throws RemoteException { + if (hasConnection(callId)) { + findConnectionForAction(callId, "onRemoteRttRequest") + .onRemoteRttRequest(); + } else { + Log.w(this, "onRemoteRttRequest called on a remote conference"); + } + } }; private final ConnectionServiceAdapterServant mServant = diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java index d8ede5c21316..429a434992c5 100644 --- a/telecomm/java/android/telecom/VideoCallImpl.java +++ b/telecomm/java/android/telecom/VideoCallImpl.java @@ -44,6 +44,7 @@ public class VideoCallImpl extends VideoCall { private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN; private int mVideoState = VideoProfile.STATE_AUDIO_ONLY; private final String mCallingPackageName; + private final int mTargetSdkVersion; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override @@ -198,13 +199,15 @@ public class VideoCallImpl extends VideoCall { private Handler mHandler; - VideoCallImpl(IVideoProvider videoProvider, String callingPackageName) throws RemoteException { + VideoCallImpl(IVideoProvider videoProvider, String callingPackageName, int targetSdkVersion) + throws RemoteException { mVideoProvider = videoProvider; mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0); mBinder = new VideoCallListenerBinder(); mVideoProvider.addVideoCallback(mBinder); mCallingPackageName = callingPackageName; + mTargetSdkVersion = targetSdkVersion; } public void destroy() { @@ -243,7 +246,7 @@ public class VideoCallImpl extends VideoCall { public void setCamera(String cameraId) { try { Log.w(this, "setCamera: cameraId=%s, calling=%s", cameraId, mCallingPackageName); - mVideoProvider.setCamera(cameraId, mCallingPackageName); + mVideoProvider.setCamera(cameraId, mCallingPackageName, mTargetSdkVersion); } catch (RemoteException e) { } } diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index 20feba78f5c9..c631d085f6e7 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -17,6 +17,7 @@ package com.android.internal.telecom; import android.os.Bundle; +import android.os.ParcelFileDescriptor; import android.telecom.CallAudioState; import android.telecom.ConnectionRequest; import android.telecom.Logging.Session; @@ -46,8 +47,8 @@ oneway interface IConnectionService { boolean isUnknown, in Session.Info sessionInfo); - void createConnectionFailed(String callId, in ConnectionRequest request, boolean isIncoming, - in Session.Info sessionInfo); + void createConnectionFailed(in PhoneAccountHandle connectionManagerPhoneAccount, String callId, + in ConnectionRequest request, boolean isIncoming, in Session.Info sessionInfo); void abort(String callId, in Session.Info sessionInfo); @@ -89,4 +90,12 @@ oneway interface IConnectionService { void sendCallEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo); void onExtrasChanged(String callId, in Bundle extras, in Session.Info sessionInfo); + + void startRtt(String callId, in ParcelFileDescriptor fromInCall, + in ParcelFileDescriptor toInCall, in Session.Info sessionInfo); + + void stopRtt(String callId, in Session.Info sessionInfo); + + void respondToRttUpgradeRequest(String callId, in ParcelFileDescriptor fromInCall, + in ParcelFileDescriptor toInCall, in Session.Info sessionInfo); } diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl index b58f8bc7a0aa..ac9da2ef9df4 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl @@ -106,4 +106,12 @@ oneway interface IConnectionServiceAdapter { void onConnectionEvent(String callId, String event, in Bundle extras, in Session.Info sessionInfo); + + void onRttInitiationSuccess(String callId, in Session.Info sessionInfo); + + void onRttInitiationFailure(String callId, int reason, in Session.Info sessionInfo); + + void onRttSessionRemotelyTerminated(String callId, in Session.Info sessionInfo); + + void onRemoteRttRequest(String callId, in Session.Info sessionInfo); } diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl index 47c3e6cfc3d5..73fa29af2ca4 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl @@ -70,11 +70,11 @@ oneway interface IInCallAdapter { void removeExtras(String callId, in List<String> keys); - void sendRttRequest(); + void sendRttRequest(String callId); - void respondToRttRequest(int id, boolean accept); + void respondToRttRequest(String callId, int id, boolean accept); - void stopRtt(); + void stopRtt(String callId); - void setRttMode(int mode); + void setRttMode(String callId, int mode); } diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl index 1f92e0c42443..e8cf8e975444 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl @@ -52,4 +52,6 @@ oneway interface IInCallService { void onConnectionEvent(String callId, String event, in Bundle extras); void onRttUpgradeRequest(String callId, int id); + + void onRttInitiationFailure(String callId, int reason); } diff --git a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl index a109e90243fe..272b884bd200 100644 --- a/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl +++ b/telecomm/java/com/android/internal/telecom/IVideoProvider.aidl @@ -30,7 +30,7 @@ oneway interface IVideoProvider { void removeVideoCallback(IBinder videoCallbackBinder); - void setCamera(String cameraId, in String mCallingPackageName); + void setCamera(String cameraId, in String mCallingPackageName, int targetSdkVersion); void setPreviewSurface(in Surface surface); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index c05045ebd18d..04443a53527c 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1176,15 +1176,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) { expectCallback(CallbackState.AVAILABLE, agent, timeoutMs); - - final boolean HAS_DATASYNC_ON_AVAILABLE = false; - if (HAS_DATASYNC_ON_AVAILABLE) { - if (expectSuspended) { - expectCallback(CallbackState.SUSPENDED, agent, timeoutMs); - } - expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs); - expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs); + if (expectSuspended) { + expectCallback(CallbackState.SUSPENDED, agent, timeoutMs); } + expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs); + expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs); } void expectAvailableCallbacks(MockNetworkAgent agent) { @@ -1196,7 +1192,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { } void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, true, TIMEOUT_MS); + expectAvailableCallbacks(agent, false, TIMEOUT_MS); expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); } @@ -1937,20 +1933,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent); dfltNetworkCallback.assertNoCallback(); - // Request a NetworkCapabilities update; only the requesting callback is notified. - // TODO: Delete this together with Connectivity{Manager,Service} code. - mCm.requestNetworkCapabilities(dfltNetworkCallback); - dfltNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent); - cellNetworkCallback.assertNoCallback(); - dfltNetworkCallback.assertNoCallback(); - - // Request a LinkProperties update; only the requesting callback is notified. - // TODO: Delete this together with Connectivity{Manager,Service} code. - mCm.requestLinkProperties(dfltNetworkCallback); - dfltNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent); - cellNetworkCallback.assertNoCallback(); - dfltNetworkCallback.assertNoCallback(); - mCm.unregisterNetworkCallback(dfltNetworkCallback); mCm.unregisterNetworkCallback(cellNetworkCallback); } diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java index fcd63ea993c8..499e58a5d4e5 100644 --- a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java @@ -40,7 +40,7 @@ public class Hyphenator_Delegate { } /*package*/ @SuppressWarnings("UnusedParameters") // TODO implement this. - static long loadHyphenator(ByteBuffer buffer, int offset) { + static long loadHyphenator(ByteBuffer buffer, int offset, int minPrefix, int minSuffix) { return sDelegateManager.addNewDelegate(new Hyphenator_Delegate()); } } diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java index 970c7d577c51..1b9901594f6e 100644 --- a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java @@ -53,8 +53,9 @@ public class StaticLayout_Delegate { } @LayoutlibDelegate - /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset) { - return Hyphenator_Delegate.loadHyphenator(buf, offset); + /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset, int minPrefix, + int minSuffix) { + return Hyphenator_Delegate.loadHyphenator(buf, offset, minPrefix, minSuffix); } @LayoutlibDelegate |