diff options
229 files changed, 8169 insertions, 4384 deletions
diff --git a/Android.bp b/Android.bp index e5d4b8b87c88..2685ac393846 100644 --- a/Android.bp +++ b/Android.bp @@ -471,8 +471,6 @@ java_library { "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl", @@ -492,6 +490,8 @@ java_library { "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl", "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl", "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl", + "telephony/java/com/android/ims/internal/IImsRegistration.aidl", + "telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl", "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl", "telephony/java/com/android/ims/internal/IImsService.aidl", "telephony/java/com/android/ims/internal/IImsServiceController.aidl", diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 5653a039a9ed..93a0fc314b7f 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -190,7 +190,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -206,7 +206,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -222,7 +222,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -238,7 +238,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR); state.resumeTiming(); @@ -254,7 +254,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PremeasuredText text = PremeasuredText.build( + final MeasuredText text = MeasuredText.build( generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR); state.resumeTiming(); diff --git a/api/current.txt b/api/current.txt index a560e967e8ad..ad78307e02b8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6403,6 +6403,7 @@ package android.app.admin { method public int getLockTaskFeatures(android.content.ComponentName); method public java.lang.String[] getLockTaskPackages(android.content.ComponentName); method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName); + method public android.content.ComponentName getMandatoryBackupTransport(); method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName); method public long getMaximumTimeToLock(android.content.ComponentName); method public int getOrganizationColor(android.content.ComponentName); @@ -6502,6 +6503,7 @@ package android.app.admin { method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException; method public void setLogoutEnabled(android.content.ComponentName, boolean); method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence); + method public void setMandatoryBackupTransport(android.content.ComponentName, android.content.ComponentName); method public void setMasterVolumeMuted(android.content.ComponentName, boolean); method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int); method public void setMaximumTimeToLock(android.content.ComponentName, long); @@ -20882,7 +20884,6 @@ package android.inputmethodservice { method public int getMaxWidth(); method public java.lang.CharSequence getTextForImeAction(int); method public android.app.Dialog getWindow(); - method public void hideSoftInputFromInputMethod(int); method public void hideStatusIcon(); method public void hideWindow(); method public boolean isExtractViewShown(); @@ -20930,6 +20931,7 @@ package android.inputmethodservice { method public void onWindowHidden(); method public void onWindowShown(); method public void requestHideSelf(int); + method public void requestShowSelf(int); method public boolean sendDefaultEditorAction(boolean); method public void sendDownUpKeyEvents(int); method public void sendKeyChar(char); @@ -20942,7 +20944,6 @@ package android.inputmethodservice { method public void setInputMethodAndSubtype(java.lang.String, android.view.inputmethod.InputMethodSubtype); method public void setInputView(android.view.View); method public boolean shouldOfferSwitchingToNextInputMethod(); - method public void showSoftInputFromInputMethod(int); method public void showStatusIcon(int); method public void showWindow(boolean); method public void switchInputMethod(java.lang.String); @@ -26218,12 +26219,18 @@ package android.net { } public final class IpSecManager { - method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; - method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; + method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException; + method public android.net.IpSecManager.SecurityParameterIndex allocateSecurityParameterIndex(java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public void applyTransportModeTransform(java.net.Socket, int, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.net.DatagramSocket, int, android.net.IpSecTransform) throws java.io.IOException; + method public void applyTransportModeTransform(java.io.FileDescriptor, int, android.net.IpSecTransform) throws java.io.IOException; method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException; - method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; + method public void removeTransportModeTransforms(java.net.Socket, android.net.IpSecTransform) throws java.io.IOException; + method public void removeTransportModeTransforms(java.net.DatagramSocket, android.net.IpSecTransform) throws java.io.IOException; + method public void removeTransportModeTransforms(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException; + field public static final int DIRECTION_IN = 0; // 0x0 + field public static final int DIRECTION_OUT = 1; // 0x1 } public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException { @@ -26246,18 +26253,15 @@ package android.net { public final class IpSecTransform implements java.lang.AutoCloseable { method public void close(); - field public static final int DIRECTION_IN = 0; // 0x0 - field public static final int DIRECTION_OUT = 1; // 0x1 } public static class IpSecTransform.Builder { ctor public IpSecTransform.Builder(android.content.Context); - method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; - method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm); - method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm); - method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm); + method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException; + method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setAuthentication(android.net.IpSecAlgorithm); + method public android.net.IpSecTransform.Builder setEncryption(android.net.IpSecAlgorithm); method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int); - method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex); } public class LinkAddress implements android.os.Parcelable { @@ -26340,10 +26344,10 @@ package android.net { } public final class MacAddress implements android.os.Parcelable { - method public int addressType(); method public int describeContents(); method public static android.net.MacAddress fromBytes(byte[]); method public static android.net.MacAddress fromString(java.lang.String); + method public int getAddressType(); method public boolean isLocallyAssigned(); method public byte[] toByteArray(); method public java.lang.String toOuiString(); @@ -26353,7 +26357,6 @@ package android.net { field public static final int TYPE_BROADCAST = 3; // 0x3 field public static final int TYPE_MULTICAST = 2; // 0x2 field public static final int TYPE_UNICAST = 1; // 0x1 - field public static final int TYPE_UNKNOWN = 0; // 0x0 } public class MailTo { @@ -32462,6 +32465,7 @@ package android.os { field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale"; field public static final java.lang.String DISALLOW_CONFIG_LOCATION_MODE = "no_config_location_mode"; field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks"; + field public static final java.lang.String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout"; field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering"; field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn"; field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi"; @@ -42296,20 +42300,9 @@ package android.text { method public boolean isAllowed(char); } - public abstract interface NoCopySpan { - } - - public static class NoCopySpan.Concrete implements android.text.NoCopySpan { - ctor public NoCopySpan.Concrete(); - } - - public abstract interface ParcelableSpan implements android.os.Parcelable { - method public abstract int getSpanTypeId(); - } - - public class PremeasuredText implements android.text.Spanned { - method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); - method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); + public class MeasuredText implements android.text.Spanned { + method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic); + method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int); method public char charAt(int); method public int getEnd(); method public android.text.TextPaint getPaint(); @@ -42328,6 +42321,17 @@ package android.text { method public java.lang.CharSequence subSequence(int, int); } + public abstract interface NoCopySpan { + } + + public static class NoCopySpan.Concrete implements android.text.NoCopySpan { + ctor public NoCopySpan.Concrete(); + } + + public abstract interface ParcelableSpan implements android.os.Parcelable { + method public abstract int getSpanTypeId(); + } + public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout); @@ -47889,7 +47893,6 @@ package android.view { field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1 field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8 field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0 - field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L field public static final int FLAGS_CHANGED = 4; // 0x4 field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1 field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000 @@ -47927,6 +47930,9 @@ package android.view { field public static final int LAST_SUB_WINDOW = 1999; // 0x7cf field public static final int LAST_SYSTEM_WINDOW = 2999; // 0xbb7 field public static final int LAYOUT_CHANGED = 1; // 0x1 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; // 0x1 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; // 0x0 + field public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; // 0x2 field public static final int MEMORY_TYPE_CHANGED = 256; // 0x100 field public static final deprecated int MEMORY_TYPE_GPU = 2; // 0x2 field public static final deprecated int MEMORY_TYPE_HARDWARE = 1; // 0x1 @@ -47989,6 +47995,7 @@ package android.view { field public int gravity; field public float horizontalMargin; field public float horizontalWeight; + field public int layoutInDisplayCutoutMode; field public deprecated int memoryType; field public java.lang.String packageName; field public int preferredDisplayModeId; @@ -48036,6 +48043,8 @@ package android.view.accessibility { method public void setPackageName(java.lang.CharSequence); method public void writeToParcel(android.os.Parcel, int); field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4 + field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10 + field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20 field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8 field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1 field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index b47304a41bc2..762b6e89f2d0 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -33,6 +33,7 @@ package android { field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; field public static final java.lang.String BRICK = "android.permission.BRICK"; + field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final deprecated java.lang.String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED"; field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED"; field public static final java.lang.String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED"; @@ -46,6 +47,7 @@ package android { field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION"; field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"; field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; + field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL"; field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"; field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE"; @@ -1092,8 +1094,38 @@ package android.hardware.camera2.params { package android.hardware.display { + public final class BrightnessChangeEvent implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR; + field public final float batteryLevel; + field public final float brightness; + field public final int colorTemperature; + field public final float lastBrightness; + field public final long[] luxTimestamps; + field public final float[] luxValues; + field public final boolean nightMode; + field public final java.lang.String packageName; + field public final long timeStamp; + } + + public final class BrightnessConfiguration implements android.os.Parcelable { + method public int describeContents(); + method public android.util.Pair<float[], float[]> getCurve(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR; + } + + public static class BrightnessConfiguration.Builder { + ctor public BrightnessConfiguration.Builder(); + method public android.hardware.display.BrightnessConfiguration build(); + method public android.hardware.display.BrightnessConfiguration.Builder setCurve(float[], float[]); + } + public final class DisplayManager { + method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents(); method public android.graphics.Point getStableDisplaySize(); + method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); } } @@ -1701,6 +1733,39 @@ package android.hardware.location { package android.hardware.radio { + public final class ProgramList implements java.lang.AutoCloseable { + method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener); + method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); + method public void close(); + method public android.hardware.radio.RadioManager.ProgramInfo get(android.hardware.radio.ProgramSelector.Identifier); + method public void registerListCallback(java.util.concurrent.Executor, android.hardware.radio.ProgramList.ListCallback); + method public void registerListCallback(android.hardware.radio.ProgramList.ListCallback); + method public void removeOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener); + method public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> toList(); + method public void unregisterListCallback(android.hardware.radio.ProgramList.ListCallback); + } + + public static final class ProgramList.Filter implements android.os.Parcelable { + ctor public ProgramList.Filter(java.util.Set<java.lang.Integer>, java.util.Set<android.hardware.radio.ProgramSelector.Identifier>, boolean, boolean); + method public boolean areCategoriesIncluded(); + method public boolean areModificationsExcluded(); + method public int describeContents(); + method public java.util.Set<java.lang.Integer> getIdentifierTypes(); + method public java.util.Set<android.hardware.radio.ProgramSelector.Identifier> getIdentifiers(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramList.Filter> CREATOR; + } + + public static abstract class ProgramList.ListCallback { + ctor public ProgramList.ListCallback(); + method public void onItemChanged(android.hardware.radio.ProgramSelector.Identifier); + method public void onItemRemoved(android.hardware.radio.ProgramSelector.Identifier); + } + + public static abstract interface ProgramList.OnCompleteListener { + method public abstract void onComplete(); + } + public final class ProgramSelector implements android.os.Parcelable { ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]); method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int); @@ -1724,6 +1789,7 @@ package android.hardware.radio { field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9 field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3 field public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4 + field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0 field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2 field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc @@ -1735,6 +1801,7 @@ package android.hardware.radio { field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6 field public static final int PROGRAM_TYPE_FM = 2; // 0x2 field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4 + field public static final int PROGRAM_TYPE_INVALID = 0; // 0x0 field public static final int PROGRAM_TYPE_SXM = 7; // 0x7 field public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf field public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8 @@ -1953,10 +2020,11 @@ package android.hardware.radio { method public abstract void cancelAnnouncement(); method public abstract void close(); method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]); + method public android.hardware.radio.ProgramList getDynamicProgramList(android.hardware.radio.ProgramList.Filter); method public abstract boolean getMute(); method public java.util.Map<java.lang.String, java.lang.String> getParameters(java.util.List<java.lang.String>); method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]); - method public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>); + method public abstract deprecated java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>); method public abstract boolean hasControl(); method public abstract deprecated boolean isAnalogForced(); method public abstract boolean isAntennaConnected(); @@ -4386,6 +4454,8 @@ package android.telephony { method public deprecated void setDataEnabled(int, boolean); method public boolean setRadio(boolean); method public boolean setRadioPower(boolean); + method public void setSimPowerState(int); + method public void setSimPowerStateForSlot(int, int); method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean); method public void setVoiceActivationState(int); method public deprecated void silenceRinger(); @@ -4395,8 +4465,6 @@ package android.telephony { method public int[] supplyPukReportResult(java.lang.String, java.lang.String); method public void toggleRadioOnOff(); method public void updateServiceLocation(); - method public void setSimPowerState(int); - method public void setSimPowerStateForSlot(int, int); field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 @@ -4579,9 +4647,12 @@ package android.util { } public final class StatsManager { + method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String); method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String); + method public byte[] getData(java.lang.String); method public byte[] getData(long); method public byte[] getMetadata(); + method public boolean removeConfiguration(java.lang.String); method public boolean removeConfiguration(long); } diff --git a/api/test-current.txt b/api/test-current.txt index 6ad4f5a8cf1f..6941731c29cd 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -295,6 +295,43 @@ package android.hardware.camera2 { } +package android.hardware.display { + + public final class BrightnessChangeEvent implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR; + field public final float batteryLevel; + field public final float brightness; + field public final int colorTemperature; + field public final float lastBrightness; + field public final long[] luxTimestamps; + field public final float[] luxValues; + field public final boolean nightMode; + field public final java.lang.String packageName; + field public final long timeStamp; + } + + public final class BrightnessConfiguration implements android.os.Parcelable { + method public int describeContents(); + method public android.util.Pair<float[], float[]> getCurve(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR; + } + + public static class BrightnessConfiguration.Builder { + ctor public BrightnessConfiguration.Builder(); + method public android.hardware.display.BrightnessConfiguration build(); + method public android.hardware.display.BrightnessConfiguration.Builder setCurve(float[], float[]); + } + + public final class DisplayManager { + method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents(); + method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); + } + +} + package android.location { public final class GnssClock implements android.os.Parcelable { diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index ffe652f72256..2c35d413631b 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -186,7 +186,8 @@ LOCAL_SRC_FILES := \ tests/statsd_test_util.cpp \ tests/e2e/WakelockDuration_e2e_test.cpp \ tests/e2e/MetricConditionLink_e2e_test.cpp \ - tests/e2e/Attribution_e2e_test.cpp + tests/e2e/Attribution_e2e_test.cpp \ + tests/e2e/GaugeMetric_e2e_test.cpp LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ @@ -198,6 +199,24 @@ LOCAL_PROTOC_OPTIMIZE_TYPE := lite include $(BUILD_NATIVE_TEST) +############################## +# stats proto static java lib +############################## + +include $(CLEAR_VARS) +LOCAL_MODULE := statsdprotolite + +LOCAL_SRC_FILES := \ + src/stats_log.proto \ + src/statsd_config.proto \ + src/atoms.proto + +LOCAL_PROTOC_OPTIMIZE_TYPE := lite + +LOCAL_STATIC_JAVA_LIBRARIES := \ + platformprotoslite + +include $(BUILD_STATIC_JAVA_LIBRARY) statsd_common_src:= statsd_common_aidl_includes:= @@ -208,4 +227,4 @@ statsd_common_shared_libraries:= ############################## -include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 09e10a13615f..301e3a535e6d 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -99,6 +99,7 @@ private: FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); }; diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h index 845c13867e5a..d0f96a2abe6b 100644 --- a/cmds/statsd/src/dimension.h +++ b/cmds/statsd/src/dimension.h @@ -32,6 +32,10 @@ namespace statsd { const DimensionsValue* getSingleLeafValue(const DimensionsValue* value); DimensionsValue getSingleLeafValue(const DimensionsValue& value); +// Appends the leaf node to the parent tree. +void appendLeafNodeToParent(const Field& field, const DimensionsValue& value, + DimensionsValue* parentValue); + // Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto // represents a tree. When the input proto has repeated fields and the input "dimensions" wants // "ANY" locations, it will return multiple trees. diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 1a4888c31fff..ae47bd898cf9 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -114,6 +114,9 @@ GaugeMetricProducer::~GaugeMetricProducer() { void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) { flushIfNeededLocked(dumpTimeNs); + ProtoOutputStream pbOutput; + onDumpReportLocked(dumpTimeNs, &pbOutput); + parseProtoOutputStream(pbOutput, report); } void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 08b0981bb14f..a0239fcd1127 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -141,6 +141,7 @@ private: FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions); FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks); FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice); + FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent); }; } // namespace statsd diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 09a43f5c881f..cee920038eef 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -43,6 +43,19 @@ int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit); // Helper function to write PulledAtomStats to ProtoOutputStream void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair, util::ProtoOutputStream* protoOutput); + +template<class T> +bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { + std::string pbBytes; + auto iter = protoOutput.data(); + while (iter.readBuffer() != NULL) { + size_t toRead = iter.currentToRead(); + pbBytes.append(reinterpret_cast<const char*>(iter.readBuffer()), toRead); + iter.rp()->move(toRead); + } + return message->ParseFromArray(pbBytes.c_str(), pbBytes.size()); +} + } // namespace statsd } // namespace os } // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp new file mode 100644 index 000000000000..10a6c363eab3 --- /dev/null +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp @@ -0,0 +1,193 @@ +// 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. + +#include <gtest/gtest.h> + +#include "src/StatsLogProcessor.h" +#include "src/stats_log_util.h" +#include "tests/statsd_test_util.h" + +#include <vector> + +namespace android { +namespace os { +namespace statsd { + +#ifdef __ANDROID__ + +namespace { + +StatsdConfig CreateStatsdConfigForPushedEvent() { + StatsdConfig config; + *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher(); + *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher(); + + auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_CHANGED); + *config.add_atom_matcher() = atomMatcher; + + auto isInBackgroundPredicate = CreateIsInBackgroundPredicate(); + *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() = + CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ }); + *config.add_predicate() = isInBackgroundPredicate; + + auto gaugeMetric = config.add_gauge_metric(); + gaugeMetric->set_id(123456); + gaugeMetric->set_what(atomMatcher.id()); + gaugeMetric->set_condition(isInBackgroundPredicate.id()); + gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false); + auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields(); + fieldMatcher->set_field(android::util::APP_START_CHANGED); + fieldMatcher->add_child()->set_field(3); // type (enum) + fieldMatcher->add_child()->set_field(4); // activity_name(str) + fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64) + *gaugeMetric->mutable_dimensions() = + CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ }); + gaugeMetric->set_bucket(ONE_MINUTE); + + auto links = gaugeMetric->add_links(); + links->set_condition(isInBackgroundPredicate.id()); + auto dimensionWhat = links->mutable_dimensions_in_what(); + dimensionWhat->set_field(android::util::APP_START_CHANGED); + dimensionWhat->add_child()->set_field(1); // uid field. + auto dimensionCondition = links->mutable_dimensions_in_condition(); + dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED); + dimensionCondition->add_child()->set_field(1); // uid field. + return config; +} + +std::unique_ptr<LogEvent> CreateAppStartChangedEvent( + const int uid, const string& pkg_name, AppStartChanged::TransitionType type, + const string& activity_name, const string& calling_pkg_name, const bool is_instant_app, + int64_t activity_start_msec, uint64_t timestampNs) { + auto logEvent = std::make_unique<LogEvent>( + android::util::APP_START_CHANGED, timestampNs); + logEvent->write(uid); + logEvent->write(pkg_name); + logEvent->write(type); + logEvent->write(activity_name); + logEvent->write(calling_pkg_name); + logEvent->write(is_instant_app); + logEvent->write(activity_start_msec); + logEvent->init(); + return logEvent; +} + +} // namespace + +TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { + auto config = CreateStatsdConfigForPushedEvent(); + int64_t bucketStartTimeNs = 10000000000; + int64_t bucketSizeNs = + TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000; + + ConfigKey cfgKey; + auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey); + EXPECT_EQ(processor->mMetricsManagers.size(), 1u); + EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid()); + + int appUid1 = 123; + int appUid2 = 456; + std::vector<std::unique_ptr<LogEvent>> events; + events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15)); + events.push_back(CreateMoveToForegroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 250)); + events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 350)); + events.push_back(CreateMoveToForegroundEvent( + appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100)); + + + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::WARM, "activity_name1", "calling_pkg_name1", + true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::HOT, "activity_name2", "calling_pkg_name2", + true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::COLD, "activity_name3", "calling_pkg_name3", + true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::WARM, "activity_name4", "calling_pkg_name4", + true /*is_instant_app*/, 104 /*activity_start_msec*/, + bucketStartTimeNs + bucketSizeNs + 30)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::COLD, "activity_name5", "calling_pkg_name5", + true /*is_instant_app*/, 105 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs)); + events.push_back(CreateAppStartChangedEvent( + appUid1, "app1", AppStartChanged::HOT, "activity_name6", "calling_pkg_name6", + false /*is_instant_app*/, 106 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs + 10)); + + events.push_back(CreateMoveToBackgroundEvent(appUid2, bucketStartTimeNs + bucketSizeNs + 10)); + events.push_back(CreateAppStartChangedEvent( + appUid2, "app2", AppStartChanged::COLD, "activity_name7", "calling_pkg_name7", + true /*is_instant_app*/, 201 /*activity_start_msec*/, + bucketStartTimeNs + 2 * bucketSizeNs + 10)); + + sortLogEventsByTimestamp(&events); + + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + ConfigMetricsReportList reports; + processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + StatsLogReport::GaugeMetricDataWrapper gaugeMetrics; + sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics); + EXPECT_EQ(gaugeMetrics.data_size(), 2); + + auto data = gaugeMetrics.data(0); + EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid1); + EXPECT_EQ(data.bucket_info_size(), 3); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::HOT); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name2"); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 102L); + + EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().type(), AppStartChanged::WARM); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_name(), "activity_name4"); + EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_start_msec(), 104L); + + EXPECT_EQ(data.bucket_info(2).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(2).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_name(), "activity_name5"); + EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_start_msec(), 105L); + + data = gaugeMetrics.data(1); + EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */); + EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid2); + EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name7"); + EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 201L); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif + +} // namespace statsd +} // namespace os +} // namespace android
\ No newline at end of file diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp index 278335674130..fcdaafce6627 100644 --- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -26,6 +26,8 @@ namespace statsd { #ifdef __ANDROID__ +namespace { + StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) { StatsdConfig config; *config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher(); @@ -56,6 +58,8 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) return config; } +} // namespace + TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { ConfigKey cfgKey; for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) { @@ -94,6 +98,7 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { auto releaseEvent2 = CreateReleaseWakelockEvent( attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15); + std::vector<std::unique_ptr<LogEvent>> events; events.push_back(std::move(screenTurnedOnEvent)); @@ -119,18 +124,8 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { auto data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::WAKELOCK_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); // Validate bucket info. EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1); data = reports.reports(0).metrics(0).duration_metrics().data(0); @@ -147,18 +142,8 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2); data = reports.reports(0).metrics(0).duration_metrics().data(0); // Validate dimension value. - EXPECT_EQ(data.dimension().field(), - android::util::WAKELOCK_STATE_CHANGED); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1); - // Attribution field. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1); - // Uid only. - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value_size(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).field(), 1); - EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0) - .value_tuple().dimensions_value(0).value_int(), 111); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); // Two output buckets. // The wakelock holding interval in the 1st bucket starts from the screen off event and to // the end of the 1st bucket. @@ -167,6 +152,32 @@ TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) { // The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and // ends at the second screen on event. EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL); + + events.clear(); + events.push_back(CreateScreenStateChangedEvent( + ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs + 90)); + events.push_back(CreateAcquireWakelockEvent( + attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100)); + events.push_back(CreateReleaseWakelockEvent( + attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100)); + sortLogEventsByTimestamp(&events); + for (const auto& event : events) { + processor->OnLogEvent(event.get()); + } + reports.Clear(); + processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports); + EXPECT_EQ(reports.reports_size(), 1); + EXPECT_EQ(reports.reports(0).metrics_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1); + EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6); + data = reports.reports(0).metrics(0).duration_metrics().data(0); + ValidateAttributionUidDimension( + data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111); + // The last wakelock holding spans 4 buckets. + EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100); + EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs); + EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL); } } diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index e7882353bec0..718b2e177d52 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -19,6 +19,14 @@ namespace android { namespace os { namespace statsd { +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) { + AtomMatcher atom_matcher; + atom_matcher.set_id(StringToId(name)); + auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher(); + simple_atom_matcher->set_atom_id(atomId); + return atom_matcher; +} + AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name, WakelockStateChanged::State state) { AtomMatcher atom_matcher; diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 1bbbd9a30540..7eb93b9d5fcd 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -23,6 +23,9 @@ namespace android { namespace os { namespace statsd { +// Create AtomMatcher proto to simply match a specific atom type. +AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId); + // Create AtomMatcher proto for acquiring wakelock. AtomMatcher CreateAcquireWakelockAtomMatcher(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e610ac4a318b..70c383b29c63 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1752,9 +1752,11 @@ public final class ActivityThread extends ClientTransactionHandler { handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1, (IVoiceInteractor) ((SomeArgs) msg.obj).arg2); break; - case ATTACH_AGENT: - handleAttachAgent((String) msg.obj); + case ATTACH_AGENT: { + Application app = getApplication(); + handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null); break; + } case APPLICATION_INFO_CHANGED: mUpdatingSystemConfig = true; try { @@ -3246,11 +3248,23 @@ public final class ActivityThread extends ClientTransactionHandler { } } - static final void handleAttachAgent(String agent) { + private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) { try { - VMDebug.attachAgent(agent); + VMDebug.attachAgent(agent, classLoader); + return true; } catch (IOException e) { - Slog.e(TAG, "Attaching agent failed: " + agent); + Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent); + return false; + } + } + + static void handleAttachAgent(String agent, LoadedApk loadedApk) { + ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null; + if (attemptAttachAgent(agent, classLoader)) { + return; + } + if (classLoader != null) { + attemptAttachAgent(agent, null); } } @@ -5542,12 +5556,16 @@ public final class ActivityThread extends ClientTransactionHandler { mCompatConfiguration = new Configuration(data.config); mProfiler = new Profiler(); + String agent = null; if (data.initProfilerInfo != null) { mProfiler.profileFile = data.initProfilerInfo.profileFile; mProfiler.profileFd = data.initProfilerInfo.profileFd; mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval; mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler; mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput; + if (data.initProfilerInfo.attachAgentDuringBind) { + agent = data.initProfilerInfo.agent; + } } // send up app name; do this *before* waiting for debugger @@ -5597,6 +5615,10 @@ public final class ActivityThread extends ClientTransactionHandler { data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + if (agent != null) { + handleAttachAgent(agent, data.loadedApk); + } + /** * Switch this process to density compatibility mode if needed. */ diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java index d5234278da7d..a295c4c61a8c 100644 --- a/core/java/android/app/ProfilerInfo.java +++ b/core/java/android/app/ProfilerInfo.java @@ -55,14 +55,24 @@ public class ProfilerInfo implements Parcelable { */ public final String agent; + /** + * Whether the {@link agent} should be attached early (before bind-application) or during + * bind-application. Agents attached prior to binding cannot be loaded from the app's APK + * directly and must be given as an absolute path (or available in the default LD_LIBRARY_PATH). + * Agents attached during bind-application will miss early setup (e.g., resource initialization + * and classloader generation), but are searched in the app's library search path. + */ + public final boolean attachAgentDuringBind; + public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop, - boolean streaming, String agent) { + boolean streaming, String agent, boolean attachAgentDuringBind) { profileFile = filename; profileFd = fd; samplingInterval = interval; autoStopProfiler = autoStop; streamingOutput = streaming; this.agent = agent; + this.attachAgentDuringBind = attachAgentDuringBind; } public ProfilerInfo(ProfilerInfo in) { @@ -72,6 +82,7 @@ public class ProfilerInfo implements Parcelable { autoStopProfiler = in.autoStopProfiler; streamingOutput = in.streamingOutput; agent = in.agent; + attachAgentDuringBind = in.attachAgentDuringBind; } /** @@ -110,6 +121,7 @@ public class ProfilerInfo implements Parcelable { out.writeInt(autoStopProfiler ? 1 : 0); out.writeInt(streamingOutput ? 1 : 0); out.writeString(agent); + out.writeBoolean(attachAgentDuringBind); } public static final Parcelable.Creator<ProfilerInfo> CREATOR = @@ -132,6 +144,7 @@ public class ProfilerInfo implements Parcelable { autoStopProfiler = in.readInt() != 0; streamingOutput = in.readInt() != 0; agent = in.readString(); + attachAgentDuringBind = in.readBoolean(); } @Override diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java index f06a9257b7f8..d511c57b1f51 100644 --- a/core/java/android/app/admin/ConnectEvent.java +++ b/core/java/android/app/admin/ConnectEvent.java @@ -68,7 +68,7 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp, + return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp, mPackageName); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e334aab7005e..67b59f6ee97d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8633,6 +8633,13 @@ public class DevicePolicyManager { * * <p> Backup service is off by default when device owner is present. * + * <p> If backups are made mandatory by specifying a non-null mandatory backup transport using + * the {@link DevicePolicyManager#setMandatoryBackupTransport} method, the backup service is + * automatically enabled. + * + * <p> If the backup service is disabled using this method after the mandatory backup transport + * has been set, the mandatory backup transport is cleared. + * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param enabled {@code true} to enable the backup service, {@code false} to disable it. * @throws SecurityException if {@code admin} is not a device owner. @@ -8664,6 +8671,43 @@ public class DevicePolicyManager { } /** + * Makes backups mandatory and enforces the usage of the specified backup transport. + * + * <p>When a {@code null} backup transport is specified, backups are made optional again. + * <p>Only device owner can call this method. + * <p>If backups were disabled and a non-null backup transport {@link ComponentName} is + * specified, backups will be enabled. + * + * @param admin admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param backupTransportComponent The backup transport layer to be used for mandatory backups. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setMandatoryBackupTransport( + @NonNull ComponentName admin, @Nullable ComponentName backupTransportComponent) { + try { + mService.setMandatoryBackupTransport(admin, backupTransportComponent); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the backup transport which has to be used for backups if backups are mandatory or + * {@code null} if backups are not mandatory. + * + * @return a {@link ComponentName} of the backup transport layer to be used if backups are + * mandatory or {@code null} if backups are not mandatory. + */ + public ComponentName getMandatoryBackupTransport() { + try { + return mService.getMandatoryBackupTransport(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + + /** * Called by a device owner to control the network logging feature. * * <p> Network logs contain DNS lookup and connect() library call events. The following library diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java index 4ddf13e07344..a2d704b86649 100644 --- a/core/java/android/app/admin/DnsEvent.java +++ b/core/java/android/app/admin/DnsEvent.java @@ -96,7 +96,7 @@ public final class DnsEvent extends NetworkEvent implements Parcelable { @Override public String toString() { - return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname, + return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname, (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses), mIpAddressesCount, mTimestamp, mPackageName); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 7154053f593c..9cdd1f836d45 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -359,6 +359,8 @@ interface IDevicePolicyManager { void setBackupServiceEnabled(in ComponentName admin, boolean enabled); boolean isBackupServiceEnabled(in ComponentName admin); + void setMandatoryBackupTransport(in ComponentName admin, in ComponentName backupTransportComponent); + ComponentName getMandatoryBackupTransport(); void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled); boolean isNetworkLoggingEnabled(in ComponentName admin); diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index da81d19ca3cb..3558e3430470 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -55,6 +55,22 @@ public class BackupTransport { // Transport should ignore its own moratoriums for call with this flag set. public static final int FLAG_USER_INITIATED = 1; + /** + * For key value backup, indicates that the backup data is a diff from a previous backup. The + * transport must apply this diff to an existing backup to build the new backup set. + * + * @hide + */ + public static final int FLAG_INCREMENTAL = 1 << 1; + + /** + * For key value backup, indicates that the backup data is a complete set, not a diff from a + * previous backup. The transport should clear any previous backup when storing this backup. + * + * @hide + */ + public static final int FLAG_NON_INCREMENTAL = 1 << 2; + IBackupTransport mBinderImpl = new TransportImpl(); public IBinder getBinder() { @@ -231,12 +247,18 @@ public class BackupTransport { * {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data * is sent and recorded successfully. * + * If the backup data is a diff against the previous backup then the flag {@link + * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup + * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will + * be set regardless of whether the backup is incremental or not. + * * @param packageInfo The identity of the application whose data is being backed up. * This specifically includes the signature list for the package. * @param inFd Descriptor of file with data that resulted from invoking the application's * BackupService.doBackup() method. This may be a pipe rather than a file on * persistent media, so it may not be seekable. - * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0. + * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link + * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0. * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far), * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this * specific package, but allow others to proceed), diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 792cb5f29f9c..f3ca74656e8c 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -294,7 +294,8 @@ interface IBackupManager { * * @param transport ComponentName of the service hosting the transport. This is different from * the transport's name that is returned by {@link BackupTransport#name()}. - * @param listener A listener object to get a callback on the transport being selected. + * @param listener A listener object to get a callback on the transport being selected. It may + * be {@code null}. * * @hide */ diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java index 5053dc6fdf05..c71bf2e65731 100644 --- a/core/java/android/app/job/JobParameters.java +++ b/core/java/android/app/job/JobParameters.java @@ -70,6 +70,7 @@ public class JobParameters implements Parcelable { private final Network network; private int stopReason; // Default value of stopReason is REASON_CANCELED + private String debugStopReason; // Human readable stop reason for debugging. /** @hide */ public JobParameters(IBinder callback, int jobId, PersistableBundle extras, @@ -104,6 +105,14 @@ public class JobParameters implements Parcelable { } /** + * Reason onStopJob() was called on this job. + * @hide + */ + public String getDebugStopReason() { + return debugStopReason; + } + + /** * @return The extras you passed in when constructing this job with * {@link android.app.job.JobInfo.Builder#setExtras(android.os.PersistableBundle)}. This will * never be null. If you did not set any extras this will be an empty bundle. @@ -288,11 +297,13 @@ public class JobParameters implements Parcelable { network = null; } stopReason = in.readInt(); + debugStopReason = in.readString(); } /** @hide */ - public void setStopReason(int reason) { + public void setStopReason(int reason, String debugStopReason) { stopReason = reason; + this.debugStopReason = debugStopReason; } @Override @@ -323,6 +334,7 @@ public class JobParameters implements Parcelable { dest.writeInt(0); } dest.writeInt(stopReason); + dest.writeString(debugStopReason); } public static final Creator<JobParameters> CREATOR = new Creator<JobParameters>() { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index a18f22e1184b..6d6c02a47082 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -6285,6 +6285,31 @@ public class PackageParser { + " " + packageName + "}"; } + public String dumpState_temp() { + String flags = ""; + flags += ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : ""); + flags += ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : ""); + if ("".equals(flags)) { + flags = "-"; + } + String privFlags = ""; + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : ""); + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : ""); + privFlags += ((applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : ""); + if ("".equals(privFlags)) { + privFlags = "-"; + } + return "Package{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + packageName + + ", ver:" + getLongVersionCode() + + ", path: " + codePath + + ", flags: " + flags + + ", privFlags: " + privFlags + + ", extra: " + (mExtras == null ? "<<NULL>>" : Integer.toHexString(System.identityHashCode(mExtras)) + "}") + + "}"; + } + @Override public int describeContents() { return 0; diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java index 0a08353cbe4c..2301824cb9dc 100644 --- a/core/java/android/hardware/display/BrightnessChangeEvent.java +++ b/core/java/android/hardware/display/BrightnessChangeEvent.java @@ -16,6 +16,8 @@ package android.hardware.display; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -23,51 +25,65 @@ import android.os.Parcelable; * Data about a brightness settings change. * * {@see DisplayManager.getBrightnessEvents()} - * TODO make this SystemAPI * @hide */ +@SystemApi +@TestApi public final class BrightnessChangeEvent implements Parcelable { /** Brightness in nits */ - public float brightness; + public final float brightness; /** Timestamp of the change {@see System.currentTimeMillis()} */ - public long timeStamp; + public final long timeStamp; /** Package name of focused activity when brightness was changed. * This will be null if the caller of {@see DisplayManager.getBrightnessEvents()} * does not have access to usage stats {@see UsageStatsManager} */ - public String packageName; + public final String packageName; /** User id of of the user running when brightness was changed. * @hide */ - public int userId; + public final int userId; /** Lux values of recent sensor data */ - public float[] luxValues; + public final float[] luxValues; /** Timestamps of the lux sensor readings {@see System.currentTimeMillis()} */ - public long[] luxTimestamps; + public final long[] luxTimestamps; /** Most recent battery level when brightness was changed or Float.NaN */ - public float batteryLevel; + public final float batteryLevel; /** Color filter active to provide night mode */ - public boolean nightMode; + public final boolean nightMode; /** If night mode color filter is active this will be the temperature in kelvin */ - public int colorTemperature; + public final int colorTemperature; - /** Brightness level before slider adjustment */ - public float lastBrightness; + /** Brightness le vel before slider adjustment */ + public final float lastBrightness; - public BrightnessChangeEvent() { + /** @hide */ + private BrightnessChangeEvent(float brightness, long timeStamp, String packageName, + int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel, + boolean nightMode, int colorTemperature, float lastBrightness) { + this.brightness = brightness; + this.timeStamp = timeStamp; + this.packageName = packageName; + this.userId = userId; + this.luxValues = luxValues; + this.luxTimestamps = luxTimestamps; + this.batteryLevel = batteryLevel; + this.nightMode = nightMode; + this.colorTemperature = colorTemperature; + this.lastBrightness = lastBrightness; } /** @hide */ - public BrightnessChangeEvent(BrightnessChangeEvent other) { + public BrightnessChangeEvent(BrightnessChangeEvent other, boolean redactPackage) { this.brightness = other.brightness; this.timeStamp = other.timeStamp; - this.packageName = other.packageName; + this.packageName = redactPackage ? null : other.packageName; this.userId = other.userId; this.luxValues = other.luxValues; this.luxTimestamps = other.luxTimestamps; @@ -118,4 +134,85 @@ public final class BrightnessChangeEvent implements Parcelable { dest.writeInt(colorTemperature); dest.writeFloat(lastBrightness); } + + /** @hide */ + public static class Builder { + private float mBrightness; + private long mTimeStamp; + private String mPackageName; + private int mUserId; + private float[] mLuxValues; + private long[] mLuxTimestamps; + private float mBatteryLevel; + private boolean mNightMode; + private int mColorTemperature; + private float mLastBrightness; + + /** {@see BrightnessChangeEvent#brightness} */ + public Builder setBrightness(float brightness) { + mBrightness = brightness; + return this; + } + + /** {@see BrightnessChangeEvent#timeStamp} */ + public Builder setTimeStamp(long timeStamp) { + mTimeStamp = timeStamp; + return this; + } + + /** {@see BrightnessChangeEvent#packageName} */ + public Builder setPackageName(String packageName) { + mPackageName = packageName; + return this; + } + + /** {@see BrightnessChangeEvent#userId} */ + public Builder setUserId(int userId) { + mUserId = userId; + return this; + } + + /** {@see BrightnessChangeEvent#luxValues} */ + public Builder setLuxValues(float[] luxValues) { + mLuxValues = luxValues; + return this; + } + + /** {@see BrightnessChangeEvent#luxTimestamps} */ + public Builder setLuxTimestamps(long[] luxTimestamps) { + mLuxTimestamps = luxTimestamps; + return this; + } + + /** {@see BrightnessChangeEvent#batteryLevel} */ + public Builder setBatteryLevel(float batteryLevel) { + mBatteryLevel = batteryLevel; + return this; + } + + /** {@see BrightnessChangeEvent#nightMode} */ + public Builder setNightMode(boolean nightMode) { + mNightMode = nightMode; + return this; + } + + /** {@see BrightnessChangeEvent#colorTemperature} */ + public Builder setColorTemperature(int colorTemperature) { + mColorTemperature = colorTemperature; + return this; + } + + /** {@see BrightnessChangeEvent#lastBrightness} */ + public Builder setLastBrightness(float lastBrightness) { + mLastBrightness = lastBrightness; + return this; + } + + /** Builds a BrightnessChangeEvent */ + public BrightnessChangeEvent build() { + return new BrightnessChangeEvent(mBrightness, mTimeStamp, + mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel, + mNightMode, mColorTemperature, mLastBrightness); + } + } } diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java index 6c3be816e87b..2156491a3e24 100644 --- a/core/java/android/hardware/display/BrightnessConfiguration.java +++ b/core/java/android/hardware/display/BrightnessConfiguration.java @@ -16,6 +16,8 @@ package android.hardware.display; +import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; @@ -25,6 +27,8 @@ import com.android.internal.util.Preconditions; import java.util.Arrays; /** @hide */ +@SystemApi +@TestApi public final class BrightnessConfiguration implements Parcelable { private final float[] mLux; private final float[] mNits; diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 97e9b9c2e2f4..76ab35d02c95 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.KeyguardManager; import android.content.Context; import android.graphics.Point; @@ -622,6 +623,8 @@ public final class DisplayManager { * Fetch {@link BrightnessChangeEvent}s. * @hide until we make it a system api. */ + @SystemApi + @TestApi @RequiresPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE) public List<BrightnessChangeEvent> getBrightnessEvents() { return mGlobal.getBrightnessEvents(mContext.getOpPackageName()); @@ -632,6 +635,9 @@ public final class DisplayManager { * * @hide */ + @SystemApi + @TestApi + @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(BrightnessConfiguration c) { setBrightnessConfigurationForUser(c, UserHandle.myUserId()); } diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl index ca380769954b..bf5e391794f5 100644 --- a/core/java/android/hardware/radio/ITuner.aidl +++ b/core/java/android/hardware/radio/ITuner.aidl @@ -17,6 +17,7 @@ package android.hardware.radio; import android.graphics.Bitmap; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; @@ -73,14 +74,8 @@ interface ITuner { */ boolean startBackgroundScan(); - /** - * @param vendorFilter Vendor-specific filter, must be Map<String, String> - * @return the list, or null if scan is in progress - * @throws IllegalArgumentException if invalid arguments are passed - * @throws IllegalStateException if the scan has not been started, client may - * call startBackgroundScan to fix this. - */ - List<RadioManager.ProgramInfo> getProgramList(in Map vendorFilter); + void startProgramListUpdates(in ProgramList.Filter filter); + void stopProgramListUpdates(); boolean isConfigFlagSupported(int flag); boolean isConfigFlagSet(int flag); diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl index 775e25c7e7cf..54af30fcc35e 100644 --- a/core/java/android/hardware/radio/ITunerCallback.aidl +++ b/core/java/android/hardware/radio/ITunerCallback.aidl @@ -16,6 +16,7 @@ package android.hardware.radio; +import android.hardware.radio.ProgramList; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; @@ -30,6 +31,7 @@ oneway interface ITunerCallback { void onBackgroundScanAvailabilityChange(boolean isAvailable); void onBackgroundScanComplete(); void onProgramListChanged(); + void onProgramListUpdated(in ProgramList.Chunk chunk); /** * @param parameters Vendor-specific key-value pairs, must be Map<String, String> diff --git a/core/java/android/hardware/radio/ProgramList.aidl b/core/java/android/hardware/radio/ProgramList.aidl new file mode 100644 index 000000000000..34b7f97558c7 --- /dev/null +++ b/core/java/android/hardware/radio/ProgramList.aidl @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2018 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.hardware.radio; + +/** @hide */ +parcelable ProgramList.Filter; + +/** @hide */ +parcelable ProgramList.Chunk; diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java new file mode 100644 index 000000000000..b2aa9ba532a9 --- /dev/null +++ b/core/java/android/hardware/radio/ProgramList.java @@ -0,0 +1,427 @@ +/** + * Copyright (C) 2018 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.hardware.radio; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +/** + * @hide + */ +@SystemApi +public final class ProgramList implements AutoCloseable { + + private final Object mLock = new Object(); + private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms = + new HashMap<>(); + + private final List<ListCallback> mListCallbacks = new ArrayList<>(); + private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>(); + private OnCloseListener mOnCloseListener; + private boolean mIsClosed = false; + private boolean mIsComplete = false; + + ProgramList() {} + + /** + * Callback for list change operations. + */ + public abstract static class ListCallback { + /** + * Called when item was modified or added to the list. + */ + public void onItemChanged(@NonNull ProgramSelector.Identifier id) { } + + /** + * Called when item was removed from the list. + */ + public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { } + } + + /** + * Listener of list complete event. + */ + public interface OnCompleteListener { + /** + * Called when the list turned complete (i.e. when the scan process + * came to an end). + */ + void onComplete(); + } + + interface OnCloseListener { + void onClose(); + } + + /** + * Registers list change callback with executor. + */ + public void registerListCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull ListCallback callback) { + registerListCallback(new ListCallback() { + public void onItemChanged(@NonNull ProgramSelector.Identifier id) { + executor.execute(() -> callback.onItemChanged(id)); + } + + public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { + executor.execute(() -> callback.onItemRemoved(id)); + } + }); + } + + /** + * Registers list change callback. + */ + public void registerListCallback(@NonNull ListCallback callback) { + synchronized (mLock) { + if (mIsClosed) return; + mListCallbacks.add(Objects.requireNonNull(callback)); + } + } + + /** + * Unregisters list change callback. + */ + public void unregisterListCallback(@NonNull ListCallback callback) { + synchronized (mLock) { + if (mIsClosed) return; + mListCallbacks.remove(Objects.requireNonNull(callback)); + } + } + + /** + * Adds list complete event listener with executor. + */ + public void addOnCompleteListener(@NonNull @CallbackExecutor Executor executor, + @NonNull OnCompleteListener listener) { + addOnCompleteListener(() -> executor.execute(listener::onComplete)); + } + + /** + * Adds list complete event listener. + */ + public void addOnCompleteListener(@NonNull OnCompleteListener listener) { + synchronized (mLock) { + if (mIsClosed) return; + mOnCompleteListeners.add(Objects.requireNonNull(listener)); + if (mIsComplete) listener.onComplete(); + } + } + + /** + * Removes list complete event listener. + */ + public void removeOnCompleteListener(@NonNull OnCompleteListener listener) { + synchronized (mLock) { + if (mIsClosed) return; + mOnCompleteListeners.remove(Objects.requireNonNull(listener)); + } + } + + void setOnCloseListener(@Nullable OnCloseListener listener) { + synchronized (mLock) { + if (mOnCloseListener != null) { + throw new IllegalStateException("Close callback is already set"); + } + mOnCloseListener = listener; + } + } + + /** + * Disables list updates and releases all resources. + */ + public void close() { + synchronized (mLock) { + if (mIsClosed) return; + mIsClosed = true; + mPrograms.clear(); + mListCallbacks.clear(); + mOnCompleteListeners.clear(); + if (mOnCloseListener != null) { + mOnCloseListener.onClose(); + mOnCloseListener = null; + } + } + } + + void apply(@NonNull Chunk chunk) { + synchronized (mLock) { + if (mIsClosed) return; + + mIsComplete = false; + + if (chunk.isPurge()) { + new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id)); + } + + chunk.getRemoved().stream().forEach(id -> removeLocked(id)); + chunk.getModified().stream().forEach(info -> putLocked(info)); + + if (chunk.isComplete()) { + mIsComplete = true; + mOnCompleteListeners.forEach(cb -> cb.onComplete()); + } + } + } + + private void putLocked(@NonNull RadioManager.ProgramInfo value) { + ProgramSelector.Identifier key = value.getSelector().getPrimaryId(); + mPrograms.put(Objects.requireNonNull(key), value); + ProgramSelector.Identifier sel = value.getSelector().getPrimaryId(); + mListCallbacks.forEach(cb -> cb.onItemChanged(sel)); + } + + private void removeLocked(@NonNull ProgramSelector.Identifier key) { + RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key)); + if (removed == null) return; + ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId(); + mListCallbacks.forEach(cb -> cb.onItemRemoved(sel)); + } + + /** + * Converts the program list in its current shape to the static List<>. + * + * @return the new List<> object; it won't receive any further updates + */ + public @NonNull List<RadioManager.ProgramInfo> toList() { + synchronized (mLock) { + return mPrograms.values().stream().collect(Collectors.toList()); + } + } + + /** + * Returns the program with a specified primary identifier. + * + * @param id primary identifier of a program to fetch + * @return the program info, or null if there is no such program on the list + */ + public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) { + synchronized (mLock) { + return mPrograms.get(Objects.requireNonNull(id)); + } + } + + /** + * Filter for the program list. + */ + public static final class Filter implements Parcelable { + private final @NonNull Set<Integer> mIdentifierTypes; + private final @NonNull Set<ProgramSelector.Identifier> mIdentifiers; + private final boolean mIncludeCategories; + private final boolean mExcludeModifications; + private final @Nullable Map<String, String> mVendorFilter; + + /** + * Constructor of program list filter. + * + * Arrays passed to this constructor become owned by this object, do not modify them later. + * + * @param identifierTypes see getIdentifierTypes() + * @param identifiers see getIdentifiers() + * @param includeCategories see areCategoriesIncluded() + * @param excludeModifications see areModificationsExcluded() + */ + public Filter(@NonNull Set<Integer> identifierTypes, + @NonNull Set<ProgramSelector.Identifier> identifiers, + boolean includeCategories, boolean excludeModifications) { + mIdentifierTypes = Objects.requireNonNull(identifierTypes); + mIdentifiers = Objects.requireNonNull(identifiers); + mIncludeCategories = includeCategories; + mExcludeModifications = excludeModifications; + mVendorFilter = null; + } + + /** + * @hide for framework use only + */ + public Filter(@Nullable Map<String, String> vendorFilter) { + mIdentifierTypes = Collections.emptySet(); + mIdentifiers = Collections.emptySet(); + mIncludeCategories = false; + mExcludeModifications = false; + mVendorFilter = vendorFilter; + } + + private Filter(@NonNull Parcel in) { + mIdentifierTypes = Utils.createIntSet(in); + mIdentifiers = Utils.createSet(in, ProgramSelector.Identifier.CREATOR); + mIncludeCategories = in.readByte() != 0; + mExcludeModifications = in.readByte() != 0; + mVendorFilter = Utils.readStringMap(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + Utils.writeIntSet(dest, mIdentifierTypes); + Utils.writeSet(dest, mIdentifiers); + dest.writeByte((byte) (mIncludeCategories ? 1 : 0)); + dest.writeByte((byte) (mExcludeModifications ? 1 : 0)); + Utils.writeStringMap(dest, mVendorFilter); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() { + public Filter createFromParcel(Parcel in) { + return new Filter(in); + } + + public Filter[] newArray(int size) { + return new Filter[size]; + } + }; + + /** + * @hide for framework use only + */ + public Map<String, String> getVendorFilter() { + return mVendorFilter; + } + + /** + * Returns the list of identifier types that satisfy the filter. + * + * If the program list entry contains at least one identifier of the type + * listed, it satisfies this condition. + * + * Empty list means no filtering on identifier type. + * + * @return the list of accepted identifier types, must not be modified + */ + public @NonNull Set<Integer> getIdentifierTypes() { + return mIdentifierTypes; + } + + /** + * Returns the list of identifiers that satisfy the filter. + * + * If the program list entry contains at least one listed identifier, + * it satisfies this condition. + * + * Empty list means no filtering on identifier. + * + * @return the list of accepted identifiers, must not be modified + */ + public @NonNull Set<ProgramSelector.Identifier> getIdentifiers() { + return mIdentifiers; + } + + /** + * Checks, if non-tunable entries that define tree structure on the + * program list (i.e. DAB ensembles) should be included. + */ + public boolean areCategoriesIncluded() { + return mIncludeCategories; + } + + /** + * Checks, if updates on entry modifications should be disabled. + * + * If true, 'modified' vector of ProgramListChunk must contain list + * additions only. Once the program is added to the list, it's not + * updated anymore. + */ + public boolean areModificationsExcluded() { + return mExcludeModifications; + } + } + + /** + * @hide This is a transport class used for internal communication between + * Broadcast Radio Service and RadioManager. + * Do not use it directly. + */ + public static final class Chunk implements Parcelable { + private final boolean mPurge; + private final boolean mComplete; + private final @NonNull Set<RadioManager.ProgramInfo> mModified; + private final @NonNull Set<ProgramSelector.Identifier> mRemoved; + + public Chunk(boolean purge, boolean complete, + @Nullable Set<RadioManager.ProgramInfo> modified, + @Nullable Set<ProgramSelector.Identifier> removed) { + mPurge = purge; + mComplete = complete; + mModified = (modified != null) ? modified : Collections.emptySet(); + mRemoved = (removed != null) ? removed : Collections.emptySet(); + } + + private Chunk(@NonNull Parcel in) { + mPurge = in.readByte() != 0; + mComplete = in.readByte() != 0; + mModified = Utils.createSet(in, RadioManager.ProgramInfo.CREATOR); + mRemoved = Utils.createSet(in, ProgramSelector.Identifier.CREATOR); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte((byte) (mPurge ? 1 : 0)); + dest.writeByte((byte) (mComplete ? 1 : 0)); + Utils.writeSet(dest, mModified); + Utils.writeSet(dest, mRemoved); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<Chunk> CREATOR = new Parcelable.Creator<Chunk>() { + public Chunk createFromParcel(Parcel in) { + return new Chunk(in); + } + + public Chunk[] newArray(int size) { + return new Chunk[size]; + } + }; + + public boolean isPurge() { + return mPurge; + } + + public boolean isComplete() { + return mComplete; + } + + public @NonNull Set<RadioManager.ProgramInfo> getModified() { + return mModified; + } + + public @NonNull Set<ProgramSelector.Identifier> getRemoved() { + return mRemoved; + } + } +} diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 2211cee9b315..3556751f4af4 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -59,6 +59,7 @@ import java.util.stream.Stream; */ @SystemApi public final class ProgramSelector implements Parcelable { + public static final int PROGRAM_TYPE_INVALID = 0; /** Analogue AM radio (with or without RDS). */ public static final int PROGRAM_TYPE_AM = 1; /** analogue FM radio (with or without RDS). */ @@ -77,6 +78,7 @@ public final class ProgramSelector implements Parcelable { public static final int PROGRAM_TYPE_VENDOR_START = 1000; public static final int PROGRAM_TYPE_VENDOR_END = 1999; @IntDef(prefix = { "PROGRAM_TYPE_" }, value = { + PROGRAM_TYPE_INVALID, PROGRAM_TYPE_AM, PROGRAM_TYPE_FM, PROGRAM_TYPE_AM_HD, @@ -89,6 +91,7 @@ public final class ProgramSelector implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface ProgramType {} + public static final int IDENTIFIER_TYPE_INVALID = 0; /** kHz */ public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1; /** 16bit */ @@ -148,6 +151,7 @@ public final class ProgramSelector implements Parcelable { public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START; public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END; @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = { + IDENTIFIER_TYPE_INVALID, IDENTIFIER_TYPE_AMFM_FREQUENCY, IDENTIFIER_TYPE_RDS_PI, IDENTIFIER_TYPE_HD_STATION_ID_EXT, @@ -268,7 +272,7 @@ public final class ProgramSelector implements Parcelable { * Vendor identifiers are passed as-is to the HAL implementation, * preserving elements order. * - * @return a array of vendor identifiers, must not be modified. + * @return an array of vendor identifiers, must not be modified. */ public @NonNull long[] getVendorIds() { return mVendorIds; diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index b740f1430157..56668ac00527 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -185,25 +185,6 @@ public class RadioManager { @Retention(RetentionPolicy.SOURCE) public @interface ConfigFlag {} - private static void writeStringMap(@NonNull Parcel dest, @NonNull Map<String, String> map) { - dest.writeInt(map.size()); - for (Map.Entry<String, String> entry : map.entrySet()) { - dest.writeString(entry.getKey()); - dest.writeString(entry.getValue()); - } - } - - private static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) { - int size = in.readInt(); - Map<String, String> map = new HashMap<>(); - while (size-- > 0) { - String key = in.readString(); - String value = in.readString(); - map.put(key, value); - } - return map; - } - /***************************************************************************** * Lists properties, options and radio bands supported by a given broadcast radio module. * Each module has a unique ID used to address it when calling RadioManager APIs. @@ -415,7 +396,7 @@ public class RadioManager { mIsBgScanSupported = in.readInt() == 1; mSupportedProgramTypes = arrayToSet(in.createIntArray()); mSupportedIdentifierTypes = arrayToSet(in.createIntArray()); - mVendorInfo = readStringMap(in); + mVendorInfo = Utils.readStringMap(in); } public static final Parcelable.Creator<ModuleProperties> CREATOR @@ -445,7 +426,7 @@ public class RadioManager { dest.writeInt(mIsBgScanSupported ? 1 : 0); dest.writeIntArray(setToArray(mSupportedProgramTypes)); dest.writeIntArray(setToArray(mSupportedIdentifierTypes)); - writeStringMap(dest, mVendorInfo); + Utils.writeStringMap(dest, mVendorInfo); } @Override @@ -1410,7 +1391,7 @@ public class RadioManager { private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3; @NonNull private final ProgramSelector mSelector; - private final boolean mTuned; + private final boolean mTuned; // TODO(b/69958777): replace with mFlags private final boolean mStereo; private final boolean mDigital; private final int mFlags; @@ -1418,7 +1399,8 @@ public class RadioManager { private final RadioMetadata mMetadata; @NonNull private final Map<String, String> mVendorInfo; - ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo, + /** @hide */ + public ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo, boolean digital, int signalStrength, RadioMetadata metadata, int flags, Map<String, String> vendorInfo) { mSelector = selector; @@ -1564,7 +1546,7 @@ public class RadioManager { mMetadata = null; } mFlags = in.readInt(); - mVendorInfo = readStringMap(in); + mVendorInfo = Utils.readStringMap(in); } public static final Parcelable.Creator<ProgramInfo> CREATOR @@ -1592,7 +1574,7 @@ public class RadioManager { mMetadata.writeToParcel(dest, flags); } dest.writeInt(mFlags); - writeStringMap(dest, mVendorInfo); + Utils.writeStringMap(dest, mVendorInfo); } @Override @@ -1727,7 +1709,8 @@ public class RadioManager { Log.e(TAG, "Failed to open tuner"); return null; } - return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID); + return new TunerAdapter(tuner, halCallback, + config != null ? config.getType() : BAND_INVALID); } @NonNull private final Context mContext; diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java index 0d367e787122..ed20c4aad761 100644 --- a/core/java/android/hardware/radio/RadioTuner.java +++ b/core/java/android/hardware/radio/RadioTuner.java @@ -280,11 +280,29 @@ public abstract class RadioTuner { * @throws IllegalStateException if the scan is in progress or has not been started, * startBackgroundScan() call may fix it. * @throws IllegalArgumentException if the vendorFilter argument is not valid. + * @deprecated Use {@link getDynamicProgramList} instead. */ + @Deprecated public abstract @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable Map<String, String> vendorFilter); /** + * Get the dynamic list of discovered radio stations. + * + * The list object is updated asynchronously; to get the updates register + * with {@link ProgramList#addListCallback}. + * + * When the returned object is no longer used, it must be closed. + * + * @param filter filter for the list, or null to get the full list. + * @return the dynamic program list object, close it after use + * or {@code null} if program list is not supported by the tuner + */ + public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { + return null; + } + + /** * Checks, if the analog playback is forced, see setAnalogForced. * * @throws IllegalStateException if the switch is not supported at current diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java index 8ad609d00816..91944bfd04f0 100644 --- a/core/java/android/hardware/radio/TunerAdapter.java +++ b/core/java/android/hardware/radio/TunerAdapter.java @@ -33,15 +33,18 @@ class TunerAdapter extends RadioTuner { private static final String TAG = "BroadcastRadio.TunerAdapter"; @NonNull private final ITuner mTuner; + @NonNull private final TunerCallbackAdapter mCallback; private boolean mIsClosed = false; private @RadioManager.Band int mBand; - TunerAdapter(ITuner tuner, @RadioManager.Band int band) { - if (tuner == null) { - throw new NullPointerException(); - } - mTuner = tuner; + private ProgramList mLegacyListProxy; + private Map<String, String> mLegacyListFilter; + + TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback, + @RadioManager.Band int band) { + mTuner = Objects.requireNonNull(tuner); + mCallback = Objects.requireNonNull(callback); mBand = band; } @@ -53,6 +56,10 @@ class TunerAdapter extends RadioTuner { return; } mIsClosed = true; + if (mLegacyListProxy != null) { + mLegacyListProxy.close(); + mLegacyListProxy = null; + } } try { mTuner.close(); @@ -227,10 +234,55 @@ class TunerAdapter extends RadioTuner { @Override public @NonNull List<RadioManager.ProgramInfo> getProgramList(@Nullable Map<String, String> vendorFilter) { - try { - return mTuner.getProgramList(vendorFilter); - } catch (RemoteException e) { - throw new RuntimeException("service died", e); + synchronized (mTuner) { + if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) { + Log.i(TAG, "Program list filter has changed, requesting new list"); + mLegacyListProxy = new ProgramList(); + mLegacyListFilter = vendorFilter; + + mCallback.clearLastCompleteList(); + mCallback.setProgramListObserver(mLegacyListProxy, () -> { }); + try { + mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter)); + } catch (RemoteException ex) { + throw new RuntimeException("service died", ex); + } + } + + List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList(); + if (list == null) throw new IllegalStateException("Program list is not ready yet"); + return list; + } + } + + @Override + public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) { + synchronized (mTuner) { + if (mLegacyListProxy != null) { + mLegacyListProxy.close(); + mLegacyListProxy = null; + } + mLegacyListFilter = null; + + ProgramList list = new ProgramList(); + mCallback.setProgramListObserver(list, () -> { + try { + mTuner.stopProgramListUpdates(); + } catch (RemoteException ex) { + Log.e(TAG, "Couldn't stop program list updates", ex); + } + }); + + try { + mTuner.startProgramListUpdates(filter); + } catch (UnsupportedOperationException ex) { + return null; + } catch (RemoteException ex) { + mCallback.setProgramListObserver(null, () -> { }); + throw new RuntimeException("service died", ex); + } + + return list; } } diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java index a01f658e80f6..b299ffe042b2 100644 --- a/core/java/android/hardware/radio/TunerCallbackAdapter.java +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -22,7 +22,9 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import java.util.List; import java.util.Map; +import java.util.Objects; /** * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback. @@ -30,9 +32,14 @@ import java.util.Map; class TunerCallbackAdapter extends ITunerCallback.Stub { private static final String TAG = "BroadcastRadio.TunerCallbackAdapter"; + private final Object mLock = new Object(); @NonNull private final RadioTuner.Callback mCallback; @NonNull private final Handler mHandler; + @Nullable ProgramList mProgramList; + @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; // for legacy getProgramList call + private boolean mDelayedCompleteCallback = false; + TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) { mCallback = callback; if (handler == null) { @@ -42,6 +49,49 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } } + void setProgramListObserver(@Nullable ProgramList programList, + @NonNull ProgramList.OnCloseListener closeListener) { + Objects.requireNonNull(closeListener); + synchronized (mLock) { + if (mProgramList != null) { + Log.w(TAG, "Previous program list observer wasn't properly closed, closing it..."); + mProgramList.close(); + } + mProgramList = programList; + if (programList == null) return; + programList.setOnCloseListener(() -> { + synchronized (mLock) { + if (mProgramList != programList) return; + mProgramList = null; + mLastCompleteList = null; + closeListener.onClose(); + } + }); + programList.addOnCompleteListener(() -> { + synchronized (mLock) { + if (mProgramList != programList) return; + mLastCompleteList = programList.toList(); + if (mDelayedCompleteCallback) { + Log.d(TAG, "Sending delayed onBackgroundScanComplete callback"); + sendBackgroundScanCompleteLocked(); + } + } + }); + } + } + + @Nullable List<RadioManager.ProgramInfo> getLastCompleteList() { + synchronized (mLock) { + return mLastCompleteList; + } + } + + void clearLastCompleteList() { + synchronized (mLock) { + mLastCompleteList = null; + } + } + @Override public void onError(int status) { mHandler.post(() -> mCallback.onError(status)); @@ -87,9 +137,22 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable)); } + private void sendBackgroundScanCompleteLocked() { + mDelayedCompleteCallback = false; + mHandler.post(() -> mCallback.onBackgroundScanComplete()); + } + @Override public void onBackgroundScanComplete() { - mHandler.post(() -> mCallback.onBackgroundScanComplete()); + synchronized (mLock) { + if (mLastCompleteList == null) { + Log.i(TAG, "Got onBackgroundScanComplete callback, but the " + + "program list didn't get through yet. Delaying it..."); + mDelayedCompleteCallback = true; + return; + } + sendBackgroundScanCompleteLocked(); + } } @Override @@ -98,6 +161,14 @@ class TunerCallbackAdapter extends ITunerCallback.Stub { } @Override + public void onProgramListUpdated(ProgramList.Chunk chunk) { + synchronized (mLock) { + if (mProgramList == null) return; + mProgramList.apply(Objects.requireNonNull(chunk)); + } + } + + @Override public void onParametersUpdated(Map parameters) { mHandler.post(() -> mCallback.onParametersUpdated(parameters)); } diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java new file mode 100644 index 000000000000..09bf8feb30c2 --- /dev/null +++ b/core/java/android/hardware/radio/Utils.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2018 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.hardware.radio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +final class Utils { + static void writeStringMap(@NonNull Parcel dest, @Nullable Map<String, String> map) { + if (map == null) { + dest.writeInt(0); + return; + } + dest.writeInt(map.size()); + for (Map.Entry<String, String> entry : map.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeString(entry.getValue()); + } + } + + static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) { + int size = in.readInt(); + Map<String, String> map = new HashMap<>(); + while (size-- > 0) { + String key = in.readString(); + String value = in.readString(); + map.put(key, value); + } + return map; + } + + static <T extends Parcelable> void writeSet(@NonNull Parcel dest, @Nullable Set<T> set) { + if (set == null) { + dest.writeInt(0); + return; + } + dest.writeInt(set.size()); + set.stream().forEach(elem -> dest.writeTypedObject(elem, 0)); + } + + static <T> Set<T> createSet(@NonNull Parcel in, Parcelable.Creator<T> c) { + int size = in.readInt(); + Set<T> set = new HashSet<>(); + while (size-- > 0) { + set.add(in.readTypedObject(c)); + } + return set; + } + + static void writeIntSet(@NonNull Parcel dest, @Nullable Set<Integer> set) { + if (set == null) { + dest.writeInt(0); + return; + } + dest.writeInt(set.size()); + set.stream().forEach(elem -> dest.writeInt(Objects.requireNonNull(elem))); + } + + static Set<Integer> createIntSet(@NonNull Parcel in) { + return createSet(in, new Parcelable.Creator<Integer>() { + public Integer createFromParcel(Parcel in) { + return in.readInt(); + } + + public Integer[] newArray(int size) { + return new Integer[size]; + } + }); + } +} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index e941279f4d25..7528bc3989c2 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1082,33 +1082,6 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Close/hide the input method's soft input area, so the user no longer - * sees it or can interact with it. This can only be called - * from the currently active input method, as validated by the given token. - * - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY}, - * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set. - */ - public void hideSoftInputFromInputMethod(int flags) { - mImm.hideSoftInputFromInputMethodInternal(mToken, flags); - } - - /** - * Show the input method's soft input area, so the user - * sees the input method window and can interact with it. - * This can only be called from the currently active input method, - * as validated by the given token. - * - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT} or - * {@link InputMethodManager#SHOW_FORCED} bit set. - */ - public void showSoftInputFromInputMethod(int flags) { - mImm.showSoftInputFromInputMethodInternal(mToken, flags); - } - - /** * Force switch to the last used input method and subtype. If the last input method didn't have * any subtypes, the framework will simply switch to the last input method with no subtype * specified. @@ -1738,7 +1711,7 @@ public class InputMethodService extends AbstractInputMethodService { // Rethrow the exception to preserve the existing behavior. Some IMEs may have directly // called this method and relied on this exception for some clean-up tasks. // TODO: Give developers a clear guideline of whether it's OK to call this method or - // InputMethodManager#showSoftInputFromInputMethod() should always be used instead. + // InputMethodService#requestShowSelf(int) should always be used instead. throw e; } finally { // TODO: Is it OK to set true when we get BadTokenException? @@ -2060,27 +2033,30 @@ public class InputMethodService extends AbstractInputMethodService { /** * Close this input method's soft input area, removing it from the display. - * The input method will continue running, but the user can no longer use - * it to generate input by touching the screen. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY - * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set. + * + * The input method will continue running, but the user can no longer use it to generate input + * by touching the screen. + * + * @see InputMethodManager#HIDE_IMPLICIT_ONLY + * @see InputMethodManager#HIDE_NOT_ALWAYS + * @param flags Provides additional operating flags. */ public void requestHideSelf(int flags) { - mImm.hideSoftInputFromInputMethod(mToken, flags); + mImm.hideSoftInputFromInputMethodInternal(mToken, flags); } - + /** - * Show the input method. This is a call back to the - * IMF to handle showing the input method. - * @param flags Provides additional operating flags. Currently may be - * 0 or have the {@link InputMethodManager#SHOW_FORCED - * InputMethodManager.} bit set. + * Show the input method's soft input area, so the user sees the input method window and can + * interact with it. + * + * @see InputMethodManager#SHOW_IMPLICIT + * @see InputMethodManager#SHOW_FORCED + * @param flags Provides additional operating flags. */ - private void requestShowSelf(int flags) { - mImm.showSoftInputFromInputMethod(mToken, flags); + public void requestShowSelf(int flags) { + mImm.showSoftInputFromInputMethodInternal(mToken, flags); } - + private boolean handleBack(boolean doIt) { if (mShowInputRequested) { // If the soft input area is shown, back closes it and we diff --git a/core/java/android/net/IIpSecService.aidl b/core/java/android/net/IIpSecService.aidl index d9b57db18071..3fe531fd7960 100644 --- a/core/java/android/net/IIpSecService.aidl +++ b/core/java/android/net/IIpSecService.aidl @@ -31,7 +31,7 @@ import android.os.ParcelFileDescriptor; interface IIpSecService { IpSecSpiResponse allocateSecurityParameterIndex( - int direction, in String remoteAddress, int requestedSpi, in IBinder binder); + in String destinationAddress, int requestedSpi, in IBinder binder); void releaseSecurityParameterIndex(int resourceId); @@ -43,7 +43,7 @@ interface IIpSecService void deleteTransportModeTransform(int transformId); - void applyTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + void applyTransportModeTransform(in ParcelFileDescriptor socket, int direction, int transformId); - void removeTransportModeTransform(in ParcelFileDescriptor socket, int transformId); + void removeTransportModeTransforms(in ParcelFileDescriptor socket, int transformId); } diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index 7d752e89e6f6..c69a4d4c0bee 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -256,13 +256,19 @@ public final class IpSecAlgorithm implements Parcelable { return getName().equals(AUTH_CRYPT_AES_GCM); } + // Because encryption keys are sensitive and userdebug builds are used by large user pools + // such as beta testers, we only allow sensitive info such as keys on eng builds. + private static boolean isUnsafeBuild() { + return Build.IS_DEBUGGABLE && Build.IS_ENG; + } + @Override public String toString() { return new StringBuilder() .append("{mName=") .append(mName) .append(", mKey=") - .append(Build.IS_DEBUGGABLE ? HexDump.toHexString(mKey) : "<hidden>") + .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>") .append(", mTruncLenBits=") .append(mTruncLenBits) .append("}") diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index f54ceb5c142a..80b0af33735b 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -32,59 +32,29 @@ public final class IpSecConfig implements Parcelable { // MODE_TRANSPORT or MODE_TUNNEL private int mMode = IpSecTransform.MODE_TRANSPORT; - // Needs to be valid only for tunnel mode // Preventing this from being null simplifies Java->Native binder - private String mLocalAddress = ""; + private String mSourceAddress = ""; // Preventing this from being null simplifies Java->Native binder - private String mRemoteAddress = ""; + private String mDestinationAddress = ""; // The underlying Network that represents the "gateway" Network // for outbound packets. It may also be used to select packets. private Network mNetwork; - /** - * This class captures the parameters that specifically apply to inbound or outbound traffic. - */ - public static class Flow { - // Minimum requirements for identifying a transform - // SPI identifying the IPsec flow in packet processing - // and a remote IP address - private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID; - - // Encryption Algorithm - private IpSecAlgorithm mEncryption; - - // Authentication Algorithm - private IpSecAlgorithm mAuthentication; - - // Authenticated Encryption Algorithm - private IpSecAlgorithm mAuthenticatedEncryption; - - @Override - public String toString() { - return new StringBuilder() - .append("{mSpiResourceId=") - .append(mSpiResourceId) - .append(", mEncryption=") - .append(mEncryption) - .append(", mAuthentication=") - .append(mAuthentication) - .append(", mAuthenticatedEncryption=") - .append(mAuthenticatedEncryption) - .append("}") - .toString(); - } - - static boolean equals(IpSecConfig.Flow lhs, IpSecConfig.Flow rhs) { - if (lhs == null || rhs == null) return (lhs == rhs); - return (lhs.mSpiResourceId == rhs.mSpiResourceId - && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption) - && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)); - } - } + // Minimum requirements for identifying a transform + // SPI identifying the IPsec SA in packet processing + // and a destination IP address + private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID; + + // Encryption Algorithm + private IpSecAlgorithm mEncryption; + + // Authentication Algorithm + private IpSecAlgorithm mAuthentication; - private final Flow[] mFlow = new Flow[] {new Flow(), new Flow()}; + // Authenticated Encryption Algorithm + private IpSecAlgorithm mAuthenticatedEncryption; // For tunnel mode IPv4 UDP Encapsulation // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE @@ -100,36 +70,37 @@ public final class IpSecConfig implements Parcelable { mMode = mode; } - /** Set the local IP address for Tunnel mode */ - public void setLocalAddress(String localAddress) { - mLocalAddress = localAddress; + /** Set the source IP addres for this IPsec transform */ + public void setSourceAddress(String sourceAddress) { + mSourceAddress = sourceAddress; } - /** Set the remote IP address for this IPsec transform */ - public void setRemoteAddress(String remoteAddress) { - mRemoteAddress = remoteAddress; + /** Set the destination IP address for this IPsec transform */ + public void setDestinationAddress(String destinationAddress) { + mDestinationAddress = destinationAddress; } - /** Set the SPI for a given direction by resource ID */ - public void setSpiResourceId(int direction, int resourceId) { - mFlow[direction].mSpiResourceId = resourceId; + /** Set the SPI by resource ID */ + public void setSpiResourceId(int resourceId) { + mSpiResourceId = resourceId; } - /** Set the encryption algorithm for a given direction */ - public void setEncryption(int direction, IpSecAlgorithm encryption) { - mFlow[direction].mEncryption = encryption; + /** Set the encryption algorithm */ + public void setEncryption(IpSecAlgorithm encryption) { + mEncryption = encryption; } - /** Set the authentication algorithm for a given direction */ - public void setAuthentication(int direction, IpSecAlgorithm authentication) { - mFlow[direction].mAuthentication = authentication; + /** Set the authentication algorithm */ + public void setAuthentication(IpSecAlgorithm authentication) { + mAuthentication = authentication; } - /** Set the authenticated encryption algorithm for a given direction */ - public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) { - mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption; + /** Set the authenticated encryption algorithm */ + public void setAuthenticatedEncryption(IpSecAlgorithm authenticatedEncryption) { + mAuthenticatedEncryption = authenticatedEncryption; } + /** Set the underlying network that will carry traffic for this transform */ public void setNetwork(Network network) { mNetwork = network; } @@ -155,28 +126,28 @@ public final class IpSecConfig implements Parcelable { return mMode; } - public String getLocalAddress() { - return mLocalAddress; + public String getSourceAddress() { + return mSourceAddress; } - public int getSpiResourceId(int direction) { - return mFlow[direction].mSpiResourceId; + public int getSpiResourceId() { + return mSpiResourceId; } - public String getRemoteAddress() { - return mRemoteAddress; + public String getDestinationAddress() { + return mDestinationAddress; } - public IpSecAlgorithm getEncryption(int direction) { - return mFlow[direction].mEncryption; + public IpSecAlgorithm getEncryption() { + return mEncryption; } - public IpSecAlgorithm getAuthentication(int direction) { - return mFlow[direction].mAuthentication; + public IpSecAlgorithm getAuthentication() { + return mAuthentication; } - public IpSecAlgorithm getAuthenticatedEncryption(int direction) { - return mFlow[direction].mAuthenticatedEncryption; + public IpSecAlgorithm getAuthenticatedEncryption() { + return mAuthenticatedEncryption; } public Network getNetwork() { @@ -209,17 +180,13 @@ public final class IpSecConfig implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(mMode); - out.writeString(mLocalAddress); - out.writeString(mRemoteAddress); + out.writeString(mSourceAddress); + out.writeString(mDestinationAddress); out.writeParcelable(mNetwork, flags); - out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags); - out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags); - out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags); + out.writeInt(mSpiResourceId); + out.writeParcelable(mEncryption, flags); + out.writeParcelable(mAuthentication, flags); + out.writeParcelable(mAuthenticatedEncryption, flags); out.writeInt(mEncapType); out.writeInt(mEncapSocketResourceId); out.writeInt(mEncapRemotePort); @@ -231,22 +198,15 @@ public final class IpSecConfig implements Parcelable { private IpSecConfig(Parcel in) { mMode = in.readInt(); - mLocalAddress = in.readString(); - mRemoteAddress = in.readString(); + mSourceAddress = in.readString(); + mDestinationAddress = in.readString(); mNetwork = (Network) in.readParcelable(Network.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId = in.readInt(); - mFlow[IpSecTransform.DIRECTION_IN].mEncryption = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mAuthentication = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption = - (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt(); - mFlow[IpSecTransform.DIRECTION_OUT].mEncryption = + mSpiResourceId = in.readInt(); + mEncryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication = + mAuthentication = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); - mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption = + mAuthenticatedEncryption = (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader()); mEncapType = in.readInt(); mEncapSocketResourceId = in.readInt(); @@ -260,10 +220,10 @@ public final class IpSecConfig implements Parcelable { strBuilder .append("{mMode=") .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT") - .append(", mLocalAddress=") - .append(mLocalAddress) - .append(", mRemoteAddress=") - .append(mRemoteAddress) + .append(", mSourceAddress=") + .append(mSourceAddress) + .append(", mDestinationAddress=") + .append(mDestinationAddress) .append(", mNetwork=") .append(mNetwork) .append(", mEncapType=") @@ -274,10 +234,14 @@ public final class IpSecConfig implements Parcelable { .append(mEncapRemotePort) .append(", mNattKeepaliveInterval=") .append(mNattKeepaliveInterval) - .append(", mFlow[OUT]=") - .append(mFlow[IpSecTransform.DIRECTION_OUT]) - .append(", mFlow[IN]=") - .append(mFlow[IpSecTransform.DIRECTION_IN]) + .append("{mSpiResourceId=") + .append(mSpiResourceId) + .append(", mEncryption=") + .append(mEncryption) + .append(", mAuthentication=") + .append(mAuthentication) + .append(", mAuthenticatedEncryption=") + .append(mAuthenticatedEncryption) .append("}"); return strBuilder.toString(); @@ -299,17 +263,18 @@ public final class IpSecConfig implements Parcelable { public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) { if (lhs == null || rhs == null) return (lhs == rhs); return (lhs.mMode == rhs.mMode - && lhs.mLocalAddress.equals(rhs.mLocalAddress) - && lhs.mRemoteAddress.equals(rhs.mRemoteAddress) + && lhs.mSourceAddress.equals(rhs.mSourceAddress) + && lhs.mDestinationAddress.equals(rhs.mDestinationAddress) && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork)) || (lhs.mNetwork == rhs.mNetwork)) && lhs.mEncapType == rhs.mEncapType && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId && lhs.mEncapRemotePort == rhs.mEncapRemotePort && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval - && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_OUT], - rhs.mFlow[IpSecTransform.DIRECTION_OUT]) - && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_IN], - rhs.mFlow[IpSecTransform.DIRECTION_IN])); + && lhs.mSpiResourceId == rhs.mSpiResourceId + && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption) + && IpSecAlgorithm.equals( + lhs.mAuthenticatedEncryption, rhs.mAuthenticatedEncryption) + && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication)); } } diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java index 34cfa9b2153d..2202df3baf92 100644 --- a/core/java/android/net/IpSecManager.java +++ b/core/java/android/net/IpSecManager.java @@ -17,6 +17,7 @@ package android.net; import static com.android.internal.util.Preconditions.checkNotNull; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemService; import android.annotation.TestApi; @@ -33,6 +34,8 @@ import dalvik.system.CloseGuard; import java.io.FileDescriptor; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.Socket; @@ -53,6 +56,23 @@ public final class IpSecManager { private static final String TAG = "IpSecManager"; /** + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic towards the host. + */ + public static final int DIRECTION_IN = 0; + + /** + * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute + * applies to traffic from the host. + */ + public static final int DIRECTION_OUT = 1; + + /** @hide */ + @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) + @Retention(RetentionPolicy.SOURCE) + public @interface PolicyDirection {} + + /** * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index. * * <p>No IPsec packet may contain an SPI of 0. @@ -125,7 +145,7 @@ public final class IpSecManager { */ public static final class SecurityParameterIndex implements AutoCloseable { private final IIpSecService mService; - private final InetAddress mRemoteAddress; + private final InetAddress mDestinationAddress; private final CloseGuard mCloseGuard = CloseGuard.get(); private int mSpi = INVALID_SECURITY_PARAMETER_INDEX; private int mResourceId = INVALID_RESOURCE_ID; @@ -164,14 +184,14 @@ public final class IpSecManager { } private SecurityParameterIndex( - @NonNull IIpSecService service, int direction, InetAddress remoteAddress, int spi) + @NonNull IIpSecService service, InetAddress destinationAddress, int spi) throws ResourceUnavailableException, SpiUnavailableException { mService = service; - mRemoteAddress = remoteAddress; + mDestinationAddress = destinationAddress; try { IpSecSpiResponse result = mService.allocateSecurityParameterIndex( - direction, remoteAddress.getHostAddress(), spi, new Binder()); + destinationAddress.getHostAddress(), spi, new Binder()); if (result == null) { throw new NullPointerException("Received null response from IpSecService"); @@ -216,25 +236,23 @@ public final class IpSecManager { } /** - * Reserve a random SPI for traffic bound to or from the specified remote address. + * Reserve a random SPI for traffic bound to or from the specified destination address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * - * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param destinationAddress the destination address for traffic bearing the requested SPI. + * For inbound traffic, the destination should be an address currently assigned on-device. * @return the reserved SecurityParameterIndex - * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated - * for this user - * @throws SpiUnavailableException indicating that a particular SPI cannot be reserved + * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are + * currently allocated for this user */ - public SecurityParameterIndex allocateSecurityParameterIndex( - int direction, InetAddress remoteAddress) throws ResourceUnavailableException { + public SecurityParameterIndex allocateSecurityParameterIndex(InetAddress destinationAddress) + throws ResourceUnavailableException { try { return new SecurityParameterIndex( mService, - direction, - remoteAddress, + destinationAddress, IpSecManager.INVALID_SECURITY_PARAMETER_INDEX); } catch (SpiUnavailableException unlikely) { throw new ResourceUnavailableException("No SPIs available"); @@ -242,26 +260,27 @@ public final class IpSecManager { } /** - * Reserve the requested SPI for traffic bound to or from the specified remote address. + * Reserve the requested SPI for traffic bound to or from the specified destination address. * * <p>If successful, this SPI is guaranteed available until released by a call to {@link * SecurityParameterIndex#close()}. * - * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT} - * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress + * @param destinationAddress the destination address for traffic bearing the requested SPI. + * For inbound traffic, the destination should be an address currently assigned on-device. * @param requestedSpi the requested SPI, or '0' to allocate a random SPI * @return the reserved SecurityParameterIndex - * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated - * for this user - * @throws SpiUnavailableException indicating that the requested SPI could not be reserved + * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are + * currently allocated for this user + * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be + * reserved */ public SecurityParameterIndex allocateSecurityParameterIndex( - int direction, InetAddress remoteAddress, int requestedSpi) + InetAddress destinationAddress, int requestedSpi) throws SpiUnavailableException, ResourceUnavailableException { if (requestedSpi == IpSecManager.INVALID_SECURITY_PARAMETER_INDEX) { throw new IllegalArgumentException("Requested SPI must be a valid (non-zero) SPI"); } - return new SecurityParameterIndex(mService, direction, remoteAddress, requestedSpi); + return new SecurityParameterIndex(mService, destinationAddress, requestedSpi); } /** @@ -269,14 +288,14 @@ public final class IpSecManager { * * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * * <p>For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * * <h4>Rekey Procedure</h4> * @@ -287,15 +306,14 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a stream socket + * @param direction the policy direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied - * @hide */ - public void applyTransportModeTransform(Socket socket, IpSecTransform transform) + public void applyTransportModeTransform( + Socket socket, int direction, IpSecTransform transform) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { - applyTransportModeTransform(pfd, transform); - } + applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform); } /** @@ -303,14 +321,14 @@ public final class IpSecManager { * * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * * <p>For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * * <h4>Rekey Procedure</h4> * @@ -321,15 +339,13 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a datagram socket + * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied - * @hide */ - public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform) - throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { - applyTransportModeTransform(pfd, transform); - } + public void applyTransportModeTransform( + DatagramSocket socket, int direction, IpSecTransform transform) throws IOException { + applyTransportModeTransform(socket.getFileDescriptor$(), direction, transform); } /** @@ -337,14 +353,14 @@ public final class IpSecManager { * * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When - * the transform is removed from the socket by calling {@link #removeTransportModeTransform}, + * the transform is removed from the socket by calling {@link #removeTransportModeTransforms}, * unprotected traffic can resume on that socket. * * <p>For security reasons, the destination address of any traffic on the socket must match the * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any * other IP address will result in an IOException. In addition, reads and writes on the socket * will throw IOException if the user deactivates the transform (by calling {@link - * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}. + * IpSecTransform#close()}) without calling {@link #removeTransportModeTransforms}. * * <h4>Rekey Procedure</h4> * @@ -355,24 +371,17 @@ public final class IpSecManager { * in-flight packets have been received. * * @param socket a socket file descriptor + * @param direction the policy direction either DIRECTION_IN or DIRECTION_OUT * @param transform a transport mode {@code IpSecTransform} * @throws IOException indicating that the transform could not be applied */ - public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform) + public void applyTransportModeTransform( + FileDescriptor socket, int direction, IpSecTransform transform) throws IOException { // We dup() the FileDescriptor here because if we don't, then the ParcelFileDescriptor() - // constructor takes control and closes the user's FD when we exit the method - // This is behaviorally the same as the other versions, but the PFD constructor does not - // dup() automatically, whereas PFD.fromSocket() and PDF.fromDatagramSocket() do dup(). + // constructor takes control and closes the user's FD when we exit the method. try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { - applyTransportModeTransform(pfd, transform); - } - } - - /* Call down to activate a transform */ - private void applyTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { - try { - mService.applyTransportModeTransform(pfd, transform.getResourceId()); + mService.applyTransportModeTransform(pfd, direction, transform.getResourceId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -407,13 +416,10 @@ public final class IpSecManager { * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket - * @hide */ - public void removeTransportModeTransform(Socket socket, IpSecTransform transform) + public void removeTransportModeTransforms(Socket socket, IpSecTransform transform) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket)) { - removeTransportModeTransform(pfd, transform); - } + removeTransportModeTransforms(socket.getFileDescriptor$(), transform); } /** @@ -430,13 +436,10 @@ public final class IpSecManager { * @param socket a socket that previously had a transform applied to it * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket - * @hide */ - public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform) + public void removeTransportModeTransforms(DatagramSocket socket, IpSecTransform transform) throws IOException { - try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket)) { - removeTransportModeTransform(pfd, transform); - } + removeTransportModeTransforms(socket.getFileDescriptor$(), transform); } /** @@ -454,17 +457,10 @@ public final class IpSecManager { * @param transform the IPsec Transform that was previously applied to the given socket * @throws IOException indicating that the transform could not be removed from the socket */ - public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform) + public void removeTransportModeTransforms(FileDescriptor socket, IpSecTransform transform) throws IOException { try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(socket)) { - removeTransportModeTransform(pfd, transform); - } - } - - /* Call down to remove a transform */ - private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) { - try { - mService.removeTransportModeTransform(pfd, transform.getResourceId()); + mService.removeTransportModeTransforms(pfd, transform.getResourceId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 102ba6d94faa..7b9b4830929d 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -38,13 +38,11 @@ import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; /** - * This class represents an IPsec transform, which comprises security associations in one or both - * directions. + * This class represents a transform, which roughly corresponds to an IPsec Security Association. * * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform} - * object encapsulates the properties and state of an inbound and outbound IPsec security - * association. That includes, but is not limited to, algorithm choice, key material, and allocated - * system resources. + * object encapsulates the properties and state of an IPsec security association. That includes, + * but is not limited to, algorithm choice, key material, and allocated system resources. * * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the * Internet Protocol</a> @@ -52,23 +50,6 @@ import java.net.InetAddress; public final class IpSecTransform implements AutoCloseable { private static final String TAG = "IpSecTransform"; - /** - * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute - * applies to traffic towards the host. - */ - public static final int DIRECTION_IN = 0; - - /** - * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute - * applies to traffic from the host. - */ - public static final int DIRECTION_OUT = 1; - - /** @hide */ - @IntDef(value = {DIRECTION_IN, DIRECTION_OUT}) - @Retention(RetentionPolicy.SOURCE) - public @interface TransformDirection {} - /** @hide */ public static final int MODE_TRANSPORT = 0; @@ -170,7 +151,7 @@ public final class IpSecTransform implements AutoCloseable { * * <p>Deactivating a transform while it is still applied to a socket will result in errors on * that socket. Make sure to remove transforms by calling {@link - * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a + * IpSecManager#removeTransportModeTransforms}. Note, removing an {@code IpSecTransform} from a * socket will not deactivate it (because one transform may be applied to multiple sockets). * * <p>It is safe to call this method on a transform that has already been deactivated. @@ -272,85 +253,49 @@ public final class IpSecTransform implements AutoCloseable { private IpSecConfig mConfig; /** - * Set the encryption algorithm for the given direction. - * - * <p>If encryption is set for a direction without also providing an SPI for that direction, - * creation of an {@code IpSecTransform} will fail when attempting to build the transform. + * Set the encryption algorithm. * * <p>Encryption is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied. */ - public IpSecTransform.Builder setEncryption( - @TransformDirection int direction, IpSecAlgorithm algo) { + public IpSecTransform.Builder setEncryption(@NonNull IpSecAlgorithm algo) { // TODO: throw IllegalArgumentException if algo is not an encryption algorithm. - mConfig.setEncryption(direction, algo); + Preconditions.checkNotNull(algo); + mConfig.setEncryption(algo); return this; } /** - * Set the authentication (integrity) algorithm for the given direction. - * - * <p>If authentication is set for a direction without also providing an SPI for that - * direction, creation of an {@code IpSecTransform} will fail when attempting to build the - * transform. + * Set the authentication (integrity) algorithm. * * <p>Authentication is mutually exclusive with authenticated encryption. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied. */ - public IpSecTransform.Builder setAuthentication( - @TransformDirection int direction, IpSecAlgorithm algo) { + public IpSecTransform.Builder setAuthentication(@NonNull IpSecAlgorithm algo) { // TODO: throw IllegalArgumentException if algo is not an authentication algorithm. - mConfig.setAuthentication(direction, algo); + Preconditions.checkNotNull(algo); + mConfig.setAuthentication(algo); return this; } /** - * Set the authenticated encryption algorithm for the given direction. - * - * <p>If an authenticated encryption algorithm is set for a given direction without also - * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when - * attempting to build the transform. + * Set the authenticated encryption algorithm. * - * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated - * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as - * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). + * <p>The Authenticated Encryption (AE) class of algorithms are also known as + * Authenticated Encryption with Associated Data (AEAD) algorithms, or Combined mode + * algorithms (as referred to in + * <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>). * * <p>Authenticated encryption is mutually exclusive with encryption and authentication. * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to * be applied. */ - public IpSecTransform.Builder setAuthenticatedEncryption( - @TransformDirection int direction, IpSecAlgorithm algo) { - mConfig.setAuthenticatedEncryption(direction, algo); - return this; - } - - /** - * Set the SPI for the given direction. - * - * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies - * packets to a given destination address. To prevent SPI collisions, values should be - * reserved by calling {@link IpSecManager#allocateSecurityParameterIndex}. - * - * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction - * will not be encrypted or authenticated. - * - * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT} - * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed - * traffic - */ - public IpSecTransform.Builder setSpi( - @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) { - if (spi.getResourceId() == INVALID_RESOURCE_ID) { - throw new IllegalArgumentException("Invalid SecurityParameterIndex"); - } - mConfig.setSpiResourceId(direction, spi.getResourceId()); + public IpSecTransform.Builder setAuthenticatedEncryption(@NonNull IpSecAlgorithm algo) { + Preconditions.checkNotNull(algo); + mConfig.setAuthenticatedEncryption(algo); return this; } @@ -363,7 +308,8 @@ public final class IpSecTransform implements AutoCloseable { * @hide */ @SystemApi - public IpSecTransform.Builder setUnderlyingNetwork(Network net) { + public IpSecTransform.Builder setUnderlyingNetwork(@NonNull Network net) { + Preconditions.checkNotNull(net); mConfig.setNetwork(net); return this; } @@ -382,7 +328,8 @@ public final class IpSecTransform implements AutoCloseable { * encapsulated traffic. In the case of IKEv2, this should be port 4500. */ public IpSecTransform.Builder setIpv4Encapsulation( - IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { + @NonNull IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) { + Preconditions.checkNotNull(localSocket); mConfig.setEncapType(ENCAP_ESPINUDP); if (localSocket.getResourceId() == INVALID_RESOURCE_ID) { throw new IllegalArgumentException("Invalid UdpEncapsulationSocket"); @@ -419,24 +366,33 @@ public final class IpSecTransform implements AutoCloseable { * will not affect any network traffic until it has been applied to one or more sockets. * * @see IpSecManager#applyTransportModeTransform - * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use - * this transform + * @param sourceAddress the source {@code InetAddress} of traffic on sockets that will use + * this transform; this address must belong to the Network used by all sockets that + * utilize this transform; if provided, then only traffic originating from the + * specified source address will be processed. + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic * @throws IllegalArgumentException indicating that a particular combination of transform * properties is invalid - * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are - * active + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms + * are active * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI * collides with an existing transform * @throws IOException indicating other errors */ - public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress) + public IpSecTransform buildTransportModeTransform( + @NonNull InetAddress sourceAddress, + @NonNull IpSecManager.SecurityParameterIndex spi) throws IpSecManager.ResourceUnavailableException, IpSecManager.SpiUnavailableException, IOException { - if (remoteAddress == null) { - throw new IllegalArgumentException("Remote address may not be null or empty!"); + Preconditions.checkNotNull(sourceAddress); + Preconditions.checkNotNull(spi); + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); } mConfig.setMode(MODE_TRANSPORT); - mConfig.setRemoteAddress(remoteAddress.getHostAddress()); + mConfig.setSourceAddress(sourceAddress.getHostAddress()); + mConfig.setSpiResourceId(spi.getResourceId()); // FIXME: modifying a builder after calling build can change the built transform. return new IpSecTransform(mContext, mConfig).activate(); } @@ -445,26 +401,33 @@ public final class IpSecTransform implements AutoCloseable { * Build and return an {@link IpSecTransform} object as a Tunnel Mode Transform. Some * parameters have interdependencies that are checked at build time. * - * @param localAddress the {@link InetAddress} that provides the local endpoint for this + * @param sourceAddress the {@link InetAddress} that provides the source address for this * IPsec tunnel. This is almost certainly an address belonging to the {@link Network} * that will originate the traffic, which is set as the {@link #setUnderlyingNetwork}. - * @param remoteAddress the {@link InetAddress} representing the remote endpoint of this - * IPsec tunnel. + * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed + * traffic * @throws IllegalArgumentException indicating that a particular combination of transform * properties is invalid. + * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms + * are active + * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI + * collides with an existing transform + * @throws IOException indicating other errors * @hide */ public IpSecTransform buildTunnelModeTransform( - InetAddress localAddress, InetAddress remoteAddress) { - if (localAddress == null) { - throw new IllegalArgumentException("Local address may not be null or empty!"); - } - if (remoteAddress == null) { - throw new IllegalArgumentException("Remote address may not be null or empty!"); + @NonNull InetAddress sourceAddress, + @NonNull IpSecManager.SecurityParameterIndex spi) + throws IpSecManager.ResourceUnavailableException, + IpSecManager.SpiUnavailableException, IOException { + Preconditions.checkNotNull(sourceAddress); + Preconditions.checkNotNull(spi); + if (spi.getResourceId() == INVALID_RESOURCE_ID) { + throw new IllegalArgumentException("Invalid SecurityParameterIndex"); } - mConfig.setLocalAddress(localAddress.getHostAddress()); - mConfig.setRemoteAddress(remoteAddress.getHostAddress()); mConfig.setMode(MODE_TUNNEL); + mConfig.setSourceAddress(sourceAddress.getHostAddress()); + mConfig.setSpiResourceId(spi.getResourceId()); return new IpSecTransform(mContext, mConfig); } diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index d6992aaede5f..287bdc88dd3e 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; @@ -60,7 +61,7 @@ public final class MacAddress implements Parcelable { }) public @interface MacAddressType { } - /** Indicates a MAC address of unknown type. */ + /** @hide Indicates a MAC address of unknown type. */ public static final int TYPE_UNKNOWN = 0; /** Indicates a MAC address is a unicast address. */ public static final int TYPE_UNICAST = 1; @@ -92,7 +93,7 @@ public final class MacAddress implements Parcelable { * * @return the int constant representing the MAC address type of this MacAddress. */ - public @MacAddressType int addressType() { + public @MacAddressType int getAddressType() { if (equals(BROADCAST_ADDRESS)) { return TYPE_BROADCAST; } @@ -120,12 +121,12 @@ public final class MacAddress implements Parcelable { /** * @return a byte array representation of this MacAddress. */ - public byte[] toByteArray() { + public @NonNull byte[] toByteArray() { return byteAddrFromLongAddr(mAddr); } @Override - public String toString() { + public @NonNull String toString() { return stringAddrFromLongAddr(mAddr); } @@ -133,7 +134,7 @@ public final class MacAddress implements Parcelable { * @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal * numbers in [0,ff] joined by ':' characters. */ - public String toOuiString() { + public @NonNull String toOuiString() { return String.format( "%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff); } @@ -197,7 +198,7 @@ public final class MacAddress implements Parcelable { if (!isMacAddress(addr)) { return TYPE_UNKNOWN; } - return MacAddress.fromBytes(addr).addressType(); + return MacAddress.fromBytes(addr).getAddressType(); } /** @@ -211,7 +212,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static byte[] byteAddrFromStringAddr(String addr) { + public static @NonNull byte[] byteAddrFromStringAddr(String addr) { Preconditions.checkNotNull(addr); String[] parts = addr.split(":"); if (parts.length != ETHER_ADDR_LEN) { @@ -239,7 +240,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static String stringAddrFromByteAddr(byte[] addr) { + public static @NonNull String stringAddrFromByteAddr(byte[] addr) { if (!isMacAddress(addr)) { return null; } @@ -291,7 +292,7 @@ public final class MacAddress implements Parcelable { // Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr)) // that avoids the allocation of an intermediary byte[]. - private static String stringAddrFromLongAddr(long addr) { + private static @NonNull String stringAddrFromLongAddr(long addr) { return String.format("%02x:%02x:%02x:%02x:%02x:%02x", (addr >> 40) & 0xff, (addr >> 32) & 0xff, @@ -310,7 +311,7 @@ public final class MacAddress implements Parcelable { * @return the MacAddress corresponding to the given String representation. * @throws IllegalArgumentException if the given String is not a valid representation. */ - public static MacAddress fromString(String addr) { + public static @NonNull MacAddress fromString(@NonNull String addr) { return new MacAddress(longAddrFromStringAddr(addr)); } @@ -322,7 +323,7 @@ public final class MacAddress implements Parcelable { * @return the MacAddress corresponding to the given byte array representation. * @throws IllegalArgumentException if the given byte array is not a valid representation. */ - public static MacAddress fromBytes(byte[] addr) { + public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) { return new MacAddress(longAddrFromByteAddr(addr)); } @@ -336,7 +337,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static MacAddress createRandomUnicastAddress() { + public static @NonNull MacAddress createRandomUnicastAddress() { return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random()); } @@ -352,7 +353,7 @@ public final class MacAddress implements Parcelable { * * @hide */ - public static MacAddress createRandomUnicastAddress(MacAddress base, Random r) { + public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) { long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()); addr = addr | LOCALLY_ASSIGNED_MASK; addr = addr & ~MULTICAST_MASK; diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 1a3ce9124b48..5df168d20586 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -357,13 +357,13 @@ public class Network implements Parcelable { // Multiple Provisioning Domains API recommendations, as made by the // IETF mif working group. // - // The HANDLE_MAGIC value MUST be kept in sync with the corresponding + // The handleMagic value MUST be kept in sync with the corresponding // value in the native/android/net.c NDK implementation. if (netId == 0) { return 0L; // make this zero condition obvious for debugging } - final long HANDLE_MAGIC = 0xfacade; - return (((long) netId) << 32) | HANDLE_MAGIC; + final long handleMagic = 0xcafed00dL; + return (((long) netId) << 32) | handleMagic; } // implement the Parcelable interface diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 81c49a339d53..9ef26a9f5a5b 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -29,7 +29,6 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.os.RemoteException; import android.os.UserHandle; -import android.telephony.SubscriptionPlan; import android.util.DebugUtils; import android.util.Pair; @@ -329,7 +328,7 @@ public class NetworkPolicyManager { * to access network when the device is idle or in battery saver mode. Otherwise, false. */ public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) { - return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } /** @@ -337,7 +336,7 @@ public class NetworkPolicyManager { * to access network when the device is in data saver mode. Otherwise, false. */ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) { - return procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; + return procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } public static String resolveNetworkId(WifiConfiguration config) { diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java index 7277ba34534b..bb36536fe2ce 100644 --- a/core/java/android/net/metrics/WakeupStats.java +++ b/core/java/android/net/metrics/WakeupStats.java @@ -80,7 +80,7 @@ public class WakeupStats { break; } - switch (ev.dstHwAddr.addressType()) { + switch (ev.dstHwAddr.getAddressType()) { case MacAddress.TYPE_UNICAST: l2UnicastCount++; break; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 21974843613f..7b0c153f5234 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -238,6 +238,20 @@ public class UserManager { public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display"; /** + * Specifies if a user is disallowed from changing screen off timeout. + * + * <p>The default value is <code>false</code>. + * + * <p>This user restriction has no effect on managed profiles. + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CONFIG_SCREEN_TIMEOUT = "no_config_screen_timeout"; + + /** * Specifies if a user is disallowed from enabling the * "Unknown Sources" setting, that allows installation of apps from unknown sources. * The default value is <code>false</code>. diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 070b8c1b0008..839a8bf42b10 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -394,4 +394,32 @@ public final class StorageVolume implements Parcelable { parcel.writeString(mFsUuid); parcel.writeString(mState); } + + /** {@hide} */ + public static final class ScopedAccessProviderContract { + + private ScopedAccessProviderContract() { + throw new UnsupportedOperationException("contains constants only"); + } + + public static final String AUTHORITY = "com.android.documentsui.scopedAccess"; + + public static final String TABLE_PACKAGES = "packages"; + public static final String TABLE_PERMISSIONS = "permissions"; + + public static final String COL_PACKAGE = "package_name"; + public static final String COL_VOLUME_UUID = "volume_uuid"; + public static final String COL_DIRECTORY = "directory"; + public static final String COL_GRANTED = "granted"; + + public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE }; + public static final String[] TABLE_PERMISSIONS_COLUMNS = + new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED }; + + public static final int TABLE_PACKAGES_COL_PACKAGE = 0; + public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0; + public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1; + public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2; + public static final int TABLE_PERMISSIONS_COL_GRANTED = 3; + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 850aedd517a9..6d91f59b2727 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6830,7 +6830,7 @@ public final class Settings { * @hide */ public static final int SHOW_ROTATION_SUGGESTIONS_DEFAULT = - SHOW_ROTATION_SUGGESTIONS_DISABLED; + SHOW_ROTATION_SUGGESTIONS_ENABLED; /** * Read only list of the service components that the current user has explicitly allowed to diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index bf4b6ac5194f..aa97b2aba749 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1917,10 +1917,10 @@ public abstract class Layout { private static float measurePara(TextPaint paint, CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - MeasuredText mt = null; + MeasuredParagraph mt = null; TextLine tl = TextLine.obtain(); try { - mt = MeasuredText.buildForBidi(text, start, end, textDir, mt); + mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt); final char[] chars = mt.getChars(); final int len = chars.length; final Directions directions = mt.getDirections(0, len); diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java new file mode 100644 index 000000000000..c93e0365d58d --- /dev/null +++ b/core/java/android/text/MeasuredParagraph.java @@ -0,0 +1,677 @@ +/* + * Copyright (C) 2010 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.text; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Paint; +import android.text.AutoGrowArray.ByteArray; +import android.text.AutoGrowArray.FloatArray; +import android.text.AutoGrowArray.IntArray; +import android.text.Layout.Directions; +import android.text.style.MetricAffectingSpan; +import android.text.style.ReplacementSpan; +import android.util.Pools.SynchronizedPool; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.util.Arrays; + +/** + * MeasuredParagraph provides text information for rendering purpose. + * + * The first motivation of this class is identify the text directions and retrieving individual + * character widths. However retrieving character widths is slower than identifying text directions. + * Thus, this class provides several builder methods for specific purposes. + * + * - buildForBidi: + * Compute only text directions. + * - buildForMeasurement: + * Compute text direction and all character widths. + * - buildForStaticLayout: + * This is bit special. StaticLayout also needs to know text direction and character widths for + * line breaking, but all things are done in native code. Similarly, text measurement is done + * in native code. So instead of storing result to Java array, this keeps the result in native + * code since there is no good reason to move the results to Java layer. + * + * In addition to the character widths, some additional information is computed for each purposes, + * e.g. whole text length for measurement or font metrics for static layout. + * + * MeasuredParagraph is NOT a thread safe object. + * @hide + */ +public class MeasuredParagraph { + private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; + + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024); + + private MeasuredParagraph() {} // Use build static functions instead. + + private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1); + + private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead. + final MeasuredParagraph mt = sPool.acquire(); + return mt != null ? mt : new MeasuredParagraph(); + } + + /** + * Recycle the MeasuredParagraph. + * + * Do not call any methods after you call this method. + */ + public void recycle() { + release(); + sPool.release(this); + } + + // The casted original text. + // + // This may be null if the passed text is not a Spanned. + private @Nullable Spanned mSpanned; + + // The start offset of the target range in the original text (mSpanned); + private @IntRange(from = 0) int mTextStart; + + // The length of the target range in the original text. + private @IntRange(from = 0) int mTextLength; + + // The copied character buffer for measuring text. + // + // The length of this array is mTextLength. + private @Nullable char[] mCopiedBuffer; + + // The whole paragraph direction. + private @Layout.Direction int mParaDir; + + // True if the text is LTR direction and doesn't contain any bidi characters. + private boolean mLtrWithoutBidi; + + // The bidi level for individual characters. + // + // This is empty if mLtrWithoutBidi is true. + private @NonNull ByteArray mLevels = new ByteArray(); + + // The whole width of the text. + // See getWholeWidth comments. + private @FloatRange(from = 0.0f) float mWholeWidth; + + // Individual characters' widths. + // See getWidths comments. + private @Nullable FloatArray mWidths = new FloatArray(); + + // The span end positions. + // See getSpanEndCache comments. + private @Nullable IntArray mSpanEndCache = new IntArray(4); + + // The font metrics. + // See getFontMetrics comments. + private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); + + // The native MeasuredParagraph. + // See getNativePtr comments. + // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. + private /* Maybe Zero */ long mNativePtr = 0; + private @Nullable Runnable mNativeObjectCleaner; + + // Associate the native object to this Java object. + private void bindNativeObject(/* Non Zero*/ long nativePtr) { + mNativePtr = nativePtr; + mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); + } + + // Decouple the native object from this Java object and release the native object. + private void unbindNativeObject() { + if (mNativePtr != 0) { + mNativeObjectCleaner.run(); + mNativePtr = 0; + } + } + + // Following two objects are for avoiding object allocation. + private @NonNull TextPaint mCachedPaint = new TextPaint(); + private @Nullable Paint.FontMetricsInt mCachedFm; + + /** + * Releases internal buffers. + */ + public void release() { + reset(); + mLevels.clearWithReleasingLargeArray(); + mWidths.clearWithReleasingLargeArray(); + mFontMetrics.clearWithReleasingLargeArray(); + mSpanEndCache.clearWithReleasingLargeArray(); + } + + /** + * Resets the internal state for starting new text. + */ + private void reset() { + mSpanned = null; + mCopiedBuffer = null; + mWholeWidth = 0; + mLevels.clear(); + mWidths.clear(); + mFontMetrics.clear(); + mSpanEndCache.clear(); + unbindNativeObject(); + } + + /** + * Returns the characters to be measured. + * + * This is always available. + */ + public @NonNull char[] getChars() { + return mCopiedBuffer; + } + + /** + * Returns the paragraph direction. + * + * This is always available. + */ + public @Layout.Direction int getParagraphDir() { + return mParaDir; + } + + /** + * Returns the directions. + * + * This is always available. + */ + public Directions getDirections(@IntRange(from = 0) int start, // inclusive + @IntRange(from = 0) int end) { // exclusive + if (mLtrWithoutBidi) { + return Layout.DIRS_ALL_LEFT_TO_RIGHT; + } + + final int length = end - start; + return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, + length); + } + + /** + * Returns the whole text width. + * + * This is available only if the MeasureText is computed with computeForMeasurement. + * Returns 0 in other cases. + */ + public @FloatRange(from = 0.0f) float getWholeWidth() { + return mWholeWidth; + } + + /** + * Returns the individual character's width. + * + * This is available only if the MeasureText is computed with computeForMeasurement. + * Returns empty array in other cases. + */ + public @NonNull FloatArray getWidths() { + return mWidths; + } + + /** + * Returns the MetricsAffectingSpan end indices. + * + * If the input text is not a spanned string, this has one value that is the length of the text. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns empty array in other cases. + */ + public @NonNull IntArray getSpanEndCache() { + return mSpanEndCache; + } + + /** + * Returns the int array which holds FontMetrics. + * + * This array holds the repeat of top, bottom, ascent, descent of font metrics value. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns empty array in other cases. + */ + public @NonNull IntArray getFontMetrics() { + return mFontMetrics; + } + + /** + * Returns the native ptr of the MeasuredParagraph. + * + * This is available only if the MeasureText is computed with computeForStaticLayout. + * Returns 0 in other cases. + */ + public /* Maybe Zero */ long getNativePtr() { + return mNativePtr; + } + + /** + * Generates new MeasuredParagraph for Bidi computation. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + return mt; + } + + /** + * Generates new MeasuredParagraph for measuring texts. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + + mt.mWidths.resize(mt.mTextLength); + if (mt.mTextLength == 0) { + return mt; + } + + if (mt.mSpanned == null) { + // No style change by MetricsAffectingSpan. Just measure all text. + mt.applyMetricsAffectingSpan( + paint, null /* spans */, start, end, 0 /* native static layout ptr */); + } else { + // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. + int spanEnd; + for (int spanStart = start; spanStart < end; spanStart = spanEnd) { + spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, + MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); + mt.applyMetricsAffectingSpan( + paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); + } + } + return mt; + } + + /** + * Generates new MeasuredParagraph for StaticLayout. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param recycle pass existing MeasuredParagraph if you want to recycle it. + * + * @return measured text + */ + public static @NonNull MeasuredParagraph buildForStaticLayout( + @NonNull TextPaint paint, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + @Nullable MeasuredParagraph recycle) { + final MeasuredParagraph mt = recycle == null ? obtain() : recycle; + mt.resetAndAnalyzeBidi(text, start, end, textDir); + if (mt.mTextLength == 0) { + // Need to build empty native measured text for StaticLayout. + // TODO: Stop creating empty measured text for empty lines. + long nativeBuilderPtr = nInitBuilder(); + try { + mt.bindNativeObject( + nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer)); + } finally { + nFreeBuilder(nativeBuilderPtr); + } + return mt; + } + + long nativeBuilderPtr = nInitBuilder(); + try { + if (mt.mSpanned == null) { + // No style change by MetricsAffectingSpan. Just measure all text. + mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); + mt.mSpanEndCache.append(end); + } else { + // There may be a MetricsAffectingSpan. Split into span transitions and apply + // styles. + int spanEnd; + for (int spanStart = start; spanStart < end; spanStart = spanEnd) { + spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, + MetricAffectingSpan.class); + MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, + MetricAffectingSpan.class); + spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, + MetricAffectingSpan.class); + mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, + nativeBuilderPtr); + mt.mSpanEndCache.append(spanEnd); + } + } + mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer)); + } finally { + nFreeBuilder(nativeBuilderPtr); + } + + return mt; + } + + /** + * Reset internal state and analyzes text for bidirectional runs. + * + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + */ + private void resetAndAnalyzeBidi(@NonNull CharSequence text, + @IntRange(from = 0) int start, // inclusive + @IntRange(from = 0) int end, // exclusive + @NonNull TextDirectionHeuristic textDir) { + reset(); + mSpanned = text instanceof Spanned ? (Spanned) text : null; + mTextStart = start; + mTextLength = end - start; + + if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { + mCopiedBuffer = new char[mTextLength]; + } + TextUtils.getChars(text, start, end, mCopiedBuffer, 0); + + // Replace characters associated with ReplacementSpan to U+FFFC. + if (mSpanned != null) { + ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); + + for (int i = 0; i < spans.length; i++) { + int startInPara = mSpanned.getSpanStart(spans[i]) - start; + int endInPara = mSpanned.getSpanEnd(spans[i]) - start; + // The span interval may be larger and must be restricted to [start, end) + if (startInPara < 0) startInPara = 0; + if (endInPara > mTextLength) endInPara = mTextLength; + Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); + } + } + + if ((textDir == TextDirectionHeuristics.LTR + || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR + || textDir == TextDirectionHeuristics.ANYRTL_LTR) + && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { + mLevels.clear(); + mParaDir = Layout.DIR_LEFT_TO_RIGHT; + mLtrWithoutBidi = true; + } else { + final int bidiRequest; + if (textDir == TextDirectionHeuristics.LTR) { + bidiRequest = Layout.DIR_REQUEST_LTR; + } else if (textDir == TextDirectionHeuristics.RTL) { + bidiRequest = Layout.DIR_REQUEST_RTL; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; + } else { + final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); + bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; + } + mLevels.resize(mTextLength); + mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); + mLtrWithoutBidi = false; + } + } + + private void applyReplacementRun(@NonNull ReplacementSpan replacement, + @IntRange(from = 0) int start, // inclusive, in copied buffer + @IntRange(from = 0) int end, // exclusive, in copied buffer + /* Maybe Zero */ long nativeBuilderPtr) { + // Use original text. Shouldn't matter. + // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for + // backward compatibility? or Should we initialize them for getFontMetricsInt? + final float width = replacement.getSize( + mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); + if (nativeBuilderPtr == 0) { + // Assigns all width to the first character. This is the same behavior as minikin. + mWidths.set(start, width); + if (end > start + 1) { + Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); + } + mWholeWidth += width; + } else { + nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, + width); + } + } + + private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer + @IntRange(from = 0) int end, // exclusive, in copied buffer + /* Maybe Zero */ long nativeBuilderPtr) { + if (nativeBuilderPtr != 0) { + mCachedPaint.getFontMetricsInt(mCachedFm); + } + + if (mLtrWithoutBidi) { + // If the whole text is LTR direction, just apply whole region. + if (nativeBuilderPtr == 0) { + mWholeWidth += mCachedPaint.getTextRunAdvances( + mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, + mWidths.getRawArray(), start); + } else { + nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, + false /* isRtl */); + } + } else { + // If there is multiple bidi levels, split into individual bidi level and apply style. + byte level = mLevels.get(start); + // Note that the empty text or empty range won't reach this method. + // Safe to search from start + 1. + for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { + if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point + final boolean isRtl = (level & 0x1) != 0; + if (nativeBuilderPtr == 0) { + final int levelLength = levelEnd - levelStart; + mWholeWidth += mCachedPaint.getTextRunAdvances( + mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, + isRtl, mWidths.getRawArray(), levelStart); + } else { + nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, + levelEnd, isRtl); + } + if (levelEnd == end) { + break; + } + levelStart = levelEnd; + level = mLevels.get(levelEnd); + } + } + } + } + + private void applyMetricsAffectingSpan( + @NonNull TextPaint paint, + @Nullable MetricAffectingSpan[] spans, + @IntRange(from = 0) int start, // inclusive, in original text buffer + @IntRange(from = 0) int end, // exclusive, in original text buffer + /* Maybe Zero */ long nativeBuilderPtr) { + mCachedPaint.set(paint); + // XXX paint should not have a baseline shift, but... + mCachedPaint.baselineShift = 0; + + final boolean needFontMetrics = nativeBuilderPtr != 0; + + if (needFontMetrics && mCachedFm == null) { + mCachedFm = new Paint.FontMetricsInt(); + } + + ReplacementSpan replacement = null; + if (spans != null) { + for (int i = 0; i < spans.length; i++) { + MetricAffectingSpan span = spans[i]; + if (span instanceof ReplacementSpan) { + // The last ReplacementSpan is effective for backward compatibility reasons. + replacement = (ReplacementSpan) span; + } else { + // TODO: No need to call updateMeasureState for ReplacementSpan as well? + span.updateMeasureState(mCachedPaint); + } + } + } + + final int startInCopiedBuffer = start - mTextStart; + final int endInCopiedBuffer = end - mTextStart; + + if (replacement != null) { + applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, + nativeBuilderPtr); + } else { + applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); + } + + if (needFontMetrics) { + if (mCachedPaint.baselineShift < 0) { + mCachedFm.ascent += mCachedPaint.baselineShift; + mCachedFm.top += mCachedPaint.baselineShift; + } else { + mCachedFm.descent += mCachedPaint.baselineShift; + mCachedFm.bottom += mCachedPaint.baselineShift; + } + + mFontMetrics.append(mCachedFm.top); + mFontMetrics.append(mCachedFm.bottom); + mFontMetrics.append(mCachedFm.ascent); + mFontMetrics.append(mCachedFm.descent); + } + } + + /** + * Returns the maximum index that the accumulated width not exceeds the width. + * + * If forward=false is passed, returns the minimum index from the end instead. + * + * This only works if the MeasuredParagraph is computed with computeForMeasurement. + * Undefined behavior in other case. + */ + @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { + float[] w = mWidths.getRawArray(); + if (forwards) { + int i = 0; + while (i < limit) { + width -= w[i]; + if (width < 0.0f) break; + i++; + } + while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; + return i; + } else { + int i = limit - 1; + while (i >= 0) { + width -= w[i]; + if (width < 0.0f) break; + i--; + } + while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { + i++; + } + return limit - i - 1; + } + } + + /** + * Returns the length of the substring. + * + * This only works if the MeasuredParagraph is computed with computeForMeasurement. + * Undefined behavior in other case. + */ + @FloatRange(from = 0.0f) float measure(int start, int limit) { + float width = 0; + float[] w = mWidths.getRawArray(); + for (int i = start; i < limit; ++i) { + width += w[i]; + } + return width; + } + + private static native /* Non Zero */ long nInitBuilder(); + + /** + * Apply style to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param isRtl True if the text is RTL. + */ + private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + boolean isRtl); + + /** + * Apply ReplacementRun to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param width The width of the replacement. + */ + private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @FloatRange(from = 0) float width); + + private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr, + @NonNull char[] text); + + private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + + @CriticalNative + private static native /* Non Zero */ long nGetReleaseFunc(); +} diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index 14d6f9e8a9ef..2c30360b81bc 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * 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. @@ -16,661 +16,255 @@ package android.text; -import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Paint; -import android.text.AutoGrowArray.ByteArray; -import android.text.AutoGrowArray.FloatArray; -import android.text.AutoGrowArray.IntArray; -import android.text.Layout.Directions; -import android.text.style.MetricAffectingSpan; -import android.text.style.ReplacementSpan; -import android.util.Pools.SynchronizedPool; +import android.util.IntArray; -import dalvik.annotation.optimization.CriticalNative; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; -import libcore.util.NativeAllocationRegistry; - -import java.util.Arrays; +import java.util.ArrayList; /** - * MeasuredText provides text information for rendering purpose. - * - * The first motivation of this class is identify the text directions and retrieving individual - * character widths. However retrieving character widths is slower than identifying text directions. - * Thus, this class provides several builder methods for specific purposes. - * - * - buildForBidi: - * Compute only text directions. - * - buildForMeasurement: - * Compute text direction and all character widths. - * - buildForStaticLayout: - * This is bit special. StaticLayout also needs to know text direction and character widths for - * line breaking, but all things are done in native code. Similarly, text measurement is done - * in native code. So instead of storing result to Java array, this keeps the result in native - * code since there is no good reason to move the results to Java layer. - * - * In addition to the character widths, some additional information is computed for each purposes, - * e.g. whole text length for measurement or font metrics for static layout. - * - * MeasuredText is NOT a thread safe object. - * @hide + * A text which has already been measured. */ -public class MeasuredText { - private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; - - private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024); - - private MeasuredText() {} // Use build static functions instead. - - private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1); - - private static @NonNull MeasuredText obtain() { // Use build static functions instead. - final MeasuredText mt = sPool.acquire(); - return mt != null ? mt : new MeasuredText(); - } - - /** - * Recycle the MeasuredText. - * - * Do not call any methods after you call this method. - */ - public void recycle() { - release(); - sPool.release(this); - } +public class MeasuredText implements Spanned { + private static final char LINE_FEED = '\n'; - // The casted original text. - // - // This may be null if the passed text is not a Spanned. - private @Nullable Spanned mSpanned; - - // The start offset of the target range in the original text (mSpanned); - private @IntRange(from = 0) int mTextStart; + // The original text. + private final @NonNull CharSequence mText; - // The length of the target range in the original text. - private @IntRange(from = 0) int mTextLength; - - // The copied character buffer for measuring text. - // - // The length of this array is mTextLength. - private @Nullable char[] mCopiedBuffer; + // The inclusive start offset of the measuring target. + private final @IntRange(from = 0) int mStart; - // The whole paragraph direction. - private @Layout.Direction int mParaDir; + // The exclusive end offset of the measuring target. + private final @IntRange(from = 0) int mEnd; - // True if the text is LTR direction and doesn't contain any bidi characters. - private boolean mLtrWithoutBidi; + // The TextPaint used for measurement. + private final @NonNull TextPaint mPaint; - // The bidi level for individual characters. - // - // This is empty if mLtrWithoutBidi is true. - private @NonNull ByteArray mLevels = new ByteArray(); - - // The whole width of the text. - // See getWholeWidth comments. - private @FloatRange(from = 0.0f) float mWholeWidth; - - // Individual characters' widths. - // See getWidths comments. - private @Nullable FloatArray mWidths = new FloatArray(); - - // The span end positions. - // See getSpanEndCache comments. - private @Nullable IntArray mSpanEndCache = new IntArray(4); - - // The font metrics. - // See getFontMetrics comments. - private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); - - // The native MeasuredText. - // See getNativePtr comments. - // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. - private /* Maybe Zero */ long mNativePtr = 0; - private @Nullable Runnable mNativeObjectCleaner; - - // Associate the native object to this Java object. - private void bindNativeObject(/* Non Zero*/ long nativePtr) { - mNativePtr = nativePtr; - mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); - } + // The requested text direction. + private final @NonNull TextDirectionHeuristic mTextDir; - // Decouple the native object from this Java object and release the native object. - private void unbindNativeObject() { - if (mNativePtr != 0) { - mNativeObjectCleaner.run(); - mNativePtr = 0; - } - } + // The measured paragraph texts. + private final @NonNull MeasuredParagraph[] mMeasuredParagraphs; - // Following two objects are for avoiding object allocation. - private @NonNull TextPaint mCachedPaint = new TextPaint(); - private @Nullable Paint.FontMetricsInt mCachedFm; - - /** - * Releases internal buffers. - */ - public void release() { - reset(); - mLevels.clearWithReleasingLargeArray(); - mWidths.clearWithReleasingLargeArray(); - mFontMetrics.clearWithReleasingLargeArray(); - mSpanEndCache.clearWithReleasingLargeArray(); - } - - /** - * Resets the internal state for starting new text. - */ - private void reset() { - mSpanned = null; - mCopiedBuffer = null; - mWholeWidth = 0; - mLevels.clear(); - mWidths.clear(); - mFontMetrics.clear(); - mSpanEndCache.clear(); - unbindNativeObject(); - } + // The sorted paragraph end offsets. + private final @NonNull int[] mParagraphBreakPoints; /** - * Returns the characters to be measured. + * Build MeasuredText from the text. * - * This is always available. + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @return The measured text. */ - public @NonNull char[] getChars() { - return mCopiedBuffer; + public static @NonNull MeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir) { + return MeasuredText.build(text, paint, textDir, 0, text.length()); } /** - * Returns the paragraph direction. + * Build MeasuredText from the specific range of the text.. * - * This is always available. + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + * @param textDir The text direction. + * @param start The inclusive start offset of the text. + * @param end The exclusive start offset of the text. + * @return The measured text. */ - public @Layout.Direction int getParagraphDir() { - return mParaDir; - } + public static @NonNull MeasuredText build(@NonNull CharSequence text, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(paint); + Preconditions.checkNotNull(textDir); + Preconditions.checkArgumentInRange(start, 0, text.length(), "start"); + Preconditions.checkArgumentInRange(end, 0, text.length(), "end"); + + final IntArray paragraphEnds = new IntArray(); + final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>(); + + int paraEnd = 0; + for (int paraStart = start; paraStart < end; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); + if (paraEnd < 0) { + // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end. + paraEnd = end; + } else { + paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. + } - /** - * Returns the directions. - * - * This is always available. - */ - public Directions getDirections(@IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end) { // exclusive - if (mLtrWithoutBidi) { - return Layout.DIRS_ALL_LEFT_TO_RIGHT; + paragraphEnds.add(paraEnd); + measuredTexts.add(MeasuredParagraph.buildForStaticLayout( + paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); } - final int length = end - start; - return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start, - length); + return new MeasuredText(text, start, end, paint, textDir, + measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]), + paragraphEnds.toArray()); } - /** - * Returns the whole text width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns 0 in other cases. - */ - public @FloatRange(from = 0.0f) float getWholeWidth() { - return mWholeWidth; + // Use MeasuredText.build instead. + private MeasuredText(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @NonNull MeasuredParagraph[] measuredTexts, + @NonNull int[] paragraphBreakPoints) { + mText = text; + mStart = start; + mEnd = end; + mPaint = paint; + mMeasuredParagraphs = measuredTexts; + mParagraphBreakPoints = paragraphBreakPoints; + mTextDir = textDir; } /** - * Returns the individual character's width. - * - * This is available only if the MeasureText is computed with computeForMeasurement. - * Returns empty array in other cases. + * Return the underlying text. */ - public @NonNull FloatArray getWidths() { - return mWidths; + public @NonNull CharSequence getText() { + return mText; } /** - * Returns the MetricsAffectingSpan end indices. - * - * If the input text is not a spanned string, this has one value that is the length of the text. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. + * Returns the inclusive start offset of measured region. */ - public @NonNull IntArray getSpanEndCache() { - return mSpanEndCache; + public @IntRange(from = 0) int getStart() { + return mStart; } /** - * Returns the int array which holds FontMetrics. - * - * This array holds the repeat of top, bottom, ascent, descent of font metrics value. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns empty array in other cases. + * Returns the exclusive end offset of measured region. */ - public @NonNull IntArray getFontMetrics() { - return mFontMetrics; + public @IntRange(from = 0) int getEnd() { + return mEnd; } /** - * Returns the native ptr of the MeasuredText. - * - * This is available only if the MeasureText is computed with computeForStaticLayout. - * Returns 0 in other cases. + * Returns the text direction associated with char sequence. */ - public /* Maybe Zero */ long getNativePtr() { - return mNativePtr; + public @NonNull TextDirectionHeuristic getTextDir() { + return mTextDir; } /** - * Generates new MeasuredText for Bidi computation. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the paint used to measure this text. */ - public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - return mt; + public @NonNull TextPaint getPaint() { + return mPaint; } /** - * Generates new MeasuredText for measuring texts. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the length of the paragraph of this text. */ - public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - - mt.mWidths.resize(mt.mTextLength); - if (mt.mTextLength == 0) { - return mt; - } - - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan( - paint, null /* spans */, start, end, 0 /* native static layout ptr */); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan( - paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); - } - } - return mt; + public @IntRange(from = 0) int getParagraphCount() { + return mParagraphBreakPoints.length; } /** - * Generates new MeasuredText for StaticLayout. - * - * If recycle is null, this returns new instance. If recycle is not null, this fills computed - * result to recycle and returns recycle. - * - * @param paint the paint to be used for rendering the text. - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction - * @param recycle pass existing MeasuredText if you want to recycle it. - * - * @return measured text + * Returns the paragraph start offset of the text. */ - public static @NonNull MeasuredText buildForStaticLayout( - @NonNull TextPaint paint, - @NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, - @Nullable MeasuredText recycle) { - final MeasuredText mt = recycle == null ? obtain() : recycle; - mt.resetAndAnalyzeBidi(text, start, end, textDir); - if (mt.mTextLength == 0) { - // Need to build empty native measured text for StaticLayout. - // TODO: Stop creating empty measured text for empty lines. - long nativeBuilderPtr = nInitBuilder(); - try { - mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); - } finally { - nFreeBuilder(nativeBuilderPtr); - } - return mt; - } - - long nativeBuilderPtr = nInitBuilder(); - try { - if (mt.mSpanned == null) { - // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); - mt.mSpanEndCache.append(end); - } else { - // There may be a MetricsAffectingSpan. Split into span transitions and apply - // styles. - int spanEnd; - for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, - MetricAffectingSpan.class); - MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, - MetricAffectingSpan.class); - spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, - MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, - nativeBuilderPtr); - mt.mSpanEndCache.append(spanEnd); - } - } - mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer)); - } finally { - nFreeBuilder(nativeBuilderPtr); - } - - return mt; + public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; } /** - * Reset internal state and analyzes text for bidirectional runs. - * - * @param text the character sequence to be measured - * @param start the inclusive start offset of the target region in the text - * @param end the exclusive end offset of the target region in the text - * @param textDir the text direction + * Returns the paragraph end offset of the text. */ - private void resetAndAnalyzeBidi(@NonNull CharSequence text, - @IntRange(from = 0) int start, // inclusive - @IntRange(from = 0) int end, // exclusive - @NonNull TextDirectionHeuristic textDir) { - reset(); - mSpanned = text instanceof Spanned ? (Spanned) text : null; - mTextStart = start; - mTextLength = end - start; - - if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) { - mCopiedBuffer = new char[mTextLength]; - } - TextUtils.getChars(text, start, end, mCopiedBuffer, 0); - - // Replace characters associated with ReplacementSpan to U+FFFC. - if (mSpanned != null) { - ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class); - - for (int i = 0; i < spans.length; i++) { - int startInPara = mSpanned.getSpanStart(spans[i]) - start; - int endInPara = mSpanned.getSpanEnd(spans[i]) - start; - // The span interval may be larger and must be restricted to [start, end) - if (startInPara < 0) startInPara = 0; - if (endInPara > mTextLength) endInPara = mTextLength; - Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER); - } - } - - if ((textDir == TextDirectionHeuristics.LTR || - textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR || - textDir == TextDirectionHeuristics.ANYRTL_LTR) && - TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) { - mLevels.clear(); - mParaDir = Layout.DIR_LEFT_TO_RIGHT; - mLtrWithoutBidi = true; - } else { - final int bidiRequest; - if (textDir == TextDirectionHeuristics.LTR) { - bidiRequest = Layout.DIR_REQUEST_LTR; - } else if (textDir == TextDirectionHeuristics.RTL) { - bidiRequest = Layout.DIR_REQUEST_RTL; - } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { - bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; - } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { - bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; - } else { - final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength); - bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; - } - mLevels.resize(mTextLength); - mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray()); - mLtrWithoutBidi = false; - } + public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return mParagraphBreakPoints[paraIndex]; } - private void applyReplacementRun(@NonNull ReplacementSpan replacement, - @IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { - // Use original text. Shouldn't matter. - // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for - // backward compatibility? or Should we initialize them for getFontMetricsInt? - final float width = replacement.getSize( - mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); - if (nativeBuilderPtr == 0) { - // Assigns all width to the first character. This is the same behavior as minikin. - mWidths.set(start, width); - if (end > start + 1) { - Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f); - } - mWholeWidth += width; - } else { - nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - width); - } + /** @hide */ + public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) { + return mMeasuredParagraphs[paraIndex]; } - private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer - @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { - if (nativeBuilderPtr != 0) { - mCachedPaint.getFontMetricsInt(mCachedFm); - } + /////////////////////////////////////////////////////////////////////////////////////////////// + // Spanned overrides + // + // Just proxy for underlying mText if appropriate. - if (mLtrWithoutBidi) { - // If the whole text is LTR direction, just apply whole region. - if (nativeBuilderPtr == 0) { - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, - mWidths.getRawArray(), start); - } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - false /* isRtl */); - } + @Override + public <T> T[] getSpans(int start, int end, Class<T> type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpans(start, end, type); } else { - // If there is multiple bidi levels, split into individual bidi level and apply style. - byte level = mLevels.get(start); - // Note that the empty text or empty range won't reach this method. - // Safe to search from start + 1. - for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { - if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point - final boolean isRtl = (level & 0x1) != 0; - if (nativeBuilderPtr == 0) { - final int levelLength = levelEnd - levelStart; - mWholeWidth += mCachedPaint.getTextRunAdvances( - mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, - isRtl, mWidths.getRawArray(), levelStart); - } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, - levelEnd, isRtl); - } - if (levelEnd == end) { - break; - } - levelStart = levelEnd; - level = mLevels.get(levelEnd); - } - } + return ArrayUtils.emptyArray(type); } } - private void applyMetricsAffectingSpan( - @NonNull TextPaint paint, - @Nullable MetricAffectingSpan[] spans, - @IntRange(from = 0) int start, // inclusive, in original text buffer - @IntRange(from = 0) int end, // exclusive, in original text buffer - /* Maybe Zero */ long nativeBuilderPtr) { - mCachedPaint.set(paint); - // XXX paint should not have a baseline shift, but... - mCachedPaint.baselineShift = 0; - - final boolean needFontMetrics = nativeBuilderPtr != 0; - - if (needFontMetrics && mCachedFm == null) { - mCachedFm = new Paint.FontMetricsInt(); - } - - ReplacementSpan replacement = null; - if (spans != null) { - for (int i = 0; i < spans.length; i++) { - MetricAffectingSpan span = spans[i]; - if (span instanceof ReplacementSpan) { - // The last ReplacementSpan is effective for backward compatibility reasons. - replacement = (ReplacementSpan) span; - } else { - // TODO: No need to call updateMeasureState for ReplacementSpan as well? - span.updateMeasureState(mCachedPaint); - } - } - } - - final int startInCopiedBuffer = start - mTextStart; - final int endInCopiedBuffer = end - mTextStart; - - if (replacement != null) { - applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, - nativeBuilderPtr); + @Override + public int getSpanStart(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanStart(tag); } else { - applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); + return -1; } + } - if (needFontMetrics) { - if (mCachedPaint.baselineShift < 0) { - mCachedFm.ascent += mCachedPaint.baselineShift; - mCachedFm.top += mCachedPaint.baselineShift; - } else { - mCachedFm.descent += mCachedPaint.baselineShift; - mCachedFm.bottom += mCachedPaint.baselineShift; - } - - mFontMetrics.append(mCachedFm.top); - mFontMetrics.append(mCachedFm.bottom); - mFontMetrics.append(mCachedFm.ascent); - mFontMetrics.append(mCachedFm.descent); + @Override + public int getSpanEnd(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanEnd(tag); + } else { + return -1; } } - /** - * Returns the maximum index that the accumulated width not exceeds the width. - * - * If forward=false is passed, returns the minimum index from the end instead. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) { - float[] w = mWidths.getRawArray(); - if (forwards) { - int i = 0; - while (i < limit) { - width -= w[i]; - if (width < 0.0f) break; - i++; - } - while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--; - return i; + @Override + public int getSpanFlags(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanFlags(tag); } else { - int i = limit - 1; - while (i >= 0) { - width -= w[i]; - if (width < 0.0f) break; - i--; - } - while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) { - i++; - } - return limit - i - 1; + return 0; } } - /** - * Returns the length of the substring. - * - * This only works if the MeasuredText is computed with computeForMeasurement. - * Undefined behavior in other case. - */ - @FloatRange(from = 0.0f) float measure(int start, int limit) { - float width = 0; - float[] w = mWidths.getRawArray(); - for (int i = start; i < limit; ++i) { - width += w[i]; + @Override + public int nextSpanTransition(int start, int limit, Class type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).nextSpanTransition(start, limit, type); + } else { + return mText.length(); } - return width; } - private static native /* Non Zero */ long nInitBuilder(); - - /** - * Apply style to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredText builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param isRtl True if the text is RTL. - */ - private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - boolean isRtl); + /////////////////////////////////////////////////////////////////////////////////////////////// + // CharSequence overrides. + // + // Just proxy for underlying mText. - /** - * Apply ReplacementRun to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredText builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param width The width of the replacement. - */ - private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @FloatRange(from = 0) float width); + @Override + public int length() { + return mText.length(); + } - private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr, - @NonNull char[] text); + @Override + public char charAt(int index) { + // TODO: Should this be index + mStart ? + return mText.charAt(index); + } - private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + @Override + public CharSequence subSequence(int start, int end) { + // TODO: return MeasuredText. + // TODO: Should this be index + mStart, end + mStart ? + return mText.subSequence(start, end); + } - @CriticalNative - private static native /* Non Zero */ long nGetReleaseFunc(); + @Override + public String toString() { + return mText.toString(); + } } diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java deleted file mode 100644 index 465314dd21ac..000000000000 --- a/core/java/android/text/PremeasuredText.java +++ /dev/null @@ -1,272 +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 android.text; - -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.util.IntArray; - -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.Preconditions; - -import java.util.ArrayList; - -/** - * A text which has already been measured. - * - * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc. - */ -public class PremeasuredText implements Spanned { - private static final char LINE_FEED = '\n'; - - // The original text. - private final @NonNull CharSequence mText; - - // The inclusive start offset of the measuring target. - private final @IntRange(from = 0) int mStart; - - // The exclusive end offset of the measuring target. - private final @IntRange(from = 0) int mEnd; - - // The TextPaint used for measurement. - private final @NonNull TextPaint mPaint; - - // The requested text direction. - private final @NonNull TextDirectionHeuristic mTextDir; - - // The measured paragraph texts. - private final @NonNull MeasuredText[] mMeasuredTexts; - - // The sorted paragraph end offsets. - private final @NonNull int[] mParagraphBreakPoints; - - /** - * Build PremeasuredText from the text. - * - * @param text The text to be measured. - * @param paint The paint to be used for drawing. - * @param textDir The text direction. - * @return The measured text. - */ - public static @NonNull PremeasuredText build(@NonNull CharSequence text, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir) { - return PremeasuredText.build(text, paint, textDir, 0, text.length()); - } - - /** - * Build PremeasuredText from the specific range of the text.. - * - * @param text The text to be measured. - * @param paint The paint to be used for drawing. - * @param textDir The text direction. - * @param start The inclusive start offset of the text. - * @param end The exclusive start offset of the text. - * @return The measured text. - */ - public static @NonNull PremeasuredText build(@NonNull CharSequence text, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end) { - Preconditions.checkNotNull(text); - Preconditions.checkNotNull(paint); - Preconditions.checkNotNull(textDir); - Preconditions.checkArgumentInRange(start, 0, text.length(), "start"); - Preconditions.checkArgumentInRange(end, 0, text.length(), "end"); - - final IntArray paragraphEnds = new IntArray(); - final ArrayList<MeasuredText> measuredTexts = new ArrayList<>(); - - int paraEnd = 0; - for (int paraStart = start; paraStart < end; paraStart = paraEnd) { - paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); - if (paraEnd < 0) { - // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end. - paraEnd = end; - } else { - paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. - } - - paragraphEnds.add(paraEnd); - measuredTexts.add(MeasuredText.buildForStaticLayout( - paint, text, paraStart, paraEnd, textDir, null /* no recycle */)); - } - - return new PremeasuredText(text, start, end, paint, textDir, - measuredTexts.toArray(new MeasuredText[measuredTexts.size()]), - paragraphEnds.toArray()); - } - - // Use PremeasuredText.build instead. - private PremeasuredText(@NonNull CharSequence text, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir, - @NonNull MeasuredText[] measuredTexts, - @NonNull int[] paragraphBreakPoints) { - mText = text; - mStart = start; - mEnd = end; - mPaint = paint; - mMeasuredTexts = measuredTexts; - mParagraphBreakPoints = paragraphBreakPoints; - mTextDir = textDir; - } - - /** - * Return the underlying text. - */ - public @NonNull CharSequence getText() { - return mText; - } - - /** - * Returns the inclusive start offset of measured region. - */ - public @IntRange(from = 0) int getStart() { - return mStart; - } - - /** - * Returns the exclusive end offset of measured region. - */ - public @IntRange(from = 0) int getEnd() { - return mEnd; - } - - /** - * Returns the text direction associated with char sequence. - */ - public @NonNull TextDirectionHeuristic getTextDir() { - return mTextDir; - } - - /** - * Returns the paint used to measure this text. - */ - public @NonNull TextPaint getPaint() { - return mPaint; - } - - /** - * Returns the length of the paragraph of this text. - */ - public @IntRange(from = 0) int getParagraphCount() { - return mParagraphBreakPoints.length; - } - - /** - * Returns the paragraph start offset of the text. - */ - public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; - } - - /** - * Returns the paragraph end offset of the text. - */ - public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return mParagraphBreakPoints[paraIndex]; - } - - /** @hide */ - public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) { - return mMeasuredTexts[paraIndex]; - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Spanned overrides - // - // Just proxy for underlying mText if appropriate. - - @Override - public <T> T[] getSpans(int start, int end, Class<T> type) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpans(start, end, type); - } else { - return ArrayUtils.emptyArray(type); - } - } - - @Override - public int getSpanStart(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanStart(tag); - } else { - return -1; - } - } - - @Override - public int getSpanEnd(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanEnd(tag); - } else { - return -1; - } - } - - @Override - public int getSpanFlags(Object tag) { - if (mText instanceof Spanned) { - return ((Spanned) mText).getSpanFlags(tag); - } else { - return 0; - } - } - - @Override - public int nextSpanTransition(int start, int limit, Class type) { - if (mText instanceof Spanned) { - return ((Spanned) mText).nextSpanTransition(start, limit, type); - } else { - return mText.length(); - } - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // CharSequence overrides. - // - // Just proxy for underlying mText. - - @Override - public int length() { - return mText.length(); - } - - @Override - public char charAt(int index) { - // TODO: Should this be index + mStart ? - return mText.charAt(index); - } - - @Override - public CharSequence subSequence(int start, int end) { - // TODO: return PremeasuredText. - // TODO: Should this be index + mStart, end + mStart ? - return mText.subSequence(start, end); - } - - @Override - public String toString() { - return mText.toString(); - } -} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index d69b1190140f..36bec863e7ac 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -55,7 +55,8 @@ public class StaticLayout extends Layout { * First, call nInit to setup native line breaker object. Then, for each paragraph, do the * following: * - * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native. + * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in + * native. * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. @@ -650,34 +651,34 @@ public class StaticLayout extends Layout { b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLeftPaddings, mRightPaddings); - PremeasuredText premeasured = null; + MeasuredText measured = null; final Spanned spanned; - if (source instanceof PremeasuredText) { - premeasured = (PremeasuredText) source; + if (source instanceof MeasuredText) { + measured = (MeasuredText) source; - final CharSequence original = premeasured.getText(); + final CharSequence original = measured.getText(); spanned = (original instanceof Spanned) ? (Spanned) original : null; - if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) { + if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) { // The buffer position has changed. Re-measure here. - premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd); + measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd); } else { - // We can use premeasured information. + // We can use measured information. - // Overwrite with the one when premeasured. + // Overwrite with the one when emeasured. // TODO: Give an option for developer not to overwrite and measure again here? - textDir = premeasured.getTextDir(); - paint = premeasured.getPaint(); + textDir = measured.getTextDir(); + paint = measured.getPaint(); } } else { - premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd); + measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd); spanned = (source instanceof Spanned) ? (Spanned) source : null; } try { - for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) { - final int paraStart = premeasured.getParagraphStart(paraIndex); - final int paraEnd = premeasured.getParagraphEnd(paraIndex); + for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) { + final int paraStart = measured.getParagraphStart(paraIndex); + final int paraEnd = measured.getParagraphEnd(paraIndex); int firstWidthLineCount = 1; int firstWidth = outerWidth; @@ -743,10 +744,10 @@ public class StaticLayout extends Layout { } } - final MeasuredText measured = premeasured.getMeasuredText(paraIndex); - final char[] chs = measured.getChars(); - final int[] spanEndCache = measured.getSpanEndCache().getRawArray(); - final int[] fmCache = measured.getFontMetrics().getRawArray(); + final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex); + final char[] chs = measuredPara.getChars(); + final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray(); + final int[] fmCache = measuredPara.getFontMetrics().getRawArray(); // TODO: Stop keeping duplicated width copy in native and Java. widths.resize(chs.length); @@ -759,7 +760,7 @@ public class StaticLayout extends Layout { // Inputs chs, - measured.getNativePtr(), + measuredPara.getNativePtr(), paraEnd - paraStart, firstWidth, firstWidthLineCount, @@ -863,7 +864,7 @@ public class StaticLayout extends Layout { v = out(source, here, endPos, ascent, descent, fmTop, fmBottom, v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, - flags[breakIndex], needMultiply, measured, bufEnd, + flags[breakIndex], needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(), paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint, moreChars); @@ -894,8 +895,8 @@ public class StaticLayout extends Layout { if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) && mLineCount < mMaximumVisibleLineCount) { - final MeasuredText measured = - MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null); + final MeasuredParagraph measuredPara = + MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null); paint.getFontMetricsInt(fm); v = out(source, bufEnd, bufEnd, fm.ascent, fm.descent, @@ -903,7 +904,7 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, null, null, fm, 0, - needMultiply, measured, bufEnd, + needMultiply, measuredPara, bufEnd, includepad, trackpad, addLastLineSpacing, null, null, bufStart, ellipsize, ellipsizedWidth, 0, paint, false); @@ -918,7 +919,7 @@ public class StaticLayout extends Layout { private int out(final CharSequence text, final int start, final int end, int above, int below, int top, int bottom, int v, final float spacingmult, final float spacingadd, final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, - final int flags, final boolean needMultiply, @NonNull final MeasuredText measured, + final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured, final int bufEnd, final boolean includePad, final boolean trackPad, final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 9c9fbf23832f..409e51438d20 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1250,10 +1250,10 @@ public class TextUtils { @NonNull String ellipsis) { final int len = text.length(); - MeasuredText mt = null; - MeasuredText resultMt = null; + MeasuredParagraph mt = null; + MeasuredParagraph resultMt = null; try { - mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); + mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt); float width = mt.getWholeWidth(); if (width <= avail) { @@ -1332,7 +1332,7 @@ public class TextUtils { if (remaining == 0) { // All text is gone. textFits = true; } else { - resultMt = MeasuredText.buildForMeasurement( + resultMt = MeasuredParagraph.buildForMeasurement( paint, result, 0, result.length(), textDir, resultMt); width = resultMt.getWholeWidth(); if (width <= avail) { @@ -1479,11 +1479,11 @@ public class TextUtils { public static CharSequence commaEllipsize(CharSequence text, TextPaint p, float avail, String oneMore, String more, TextDirectionHeuristic textDir) { - MeasuredText mt = null; - MeasuredText tempMt = null; + MeasuredParagraph mt = null; + MeasuredParagraph tempMt = null; try { int len = text.length(); - mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt); + mt = MeasuredParagraph.buildForMeasurement(p, text, 0, len, textDir, mt); final float width = mt.getWholeWidth(); if (width <= avail) { return text; @@ -1523,7 +1523,7 @@ public class TextUtils { } // XXX this is probably ok, but need to look at it more - tempMt = MeasuredText.buildForMeasurement( + tempMt = MeasuredParagraph.buildForMeasurement( p, format, 0, format.length(), textDir, tempMt); float moreWid = tempMt.getWholeWidth(); diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 62f971724673..82421566f7d3 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -38,7 +38,6 @@ public class FeatureFlagUtils { static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("device_info_v2", "true"); - DEFAULT_FLAGS.put("settings_search_v2", "true"); DEFAULT_FLAGS.put("settings_app_info_v2", "true"); DEFAULT_FLAGS.put("settings_connected_device_v2", "true"); DEFAULT_FLAGS.put("settings_battery_v2", "false"); diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java index c25b272c11b9..e0d085cfaa66 100644 --- a/core/java/android/util/StatsManager.java +++ b/core/java/android/util/StatsManager.java @@ -42,6 +42,16 @@ public final class StatsManager { } /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean addConfiguration(String configKey, byte[] config, String pkg, String cls) { + // To prevent breakages of dependencies on old API. + + return false; + } + + /** * Clients can send a configuration and simultaneously registers the name of a broadcast * receiver that listens for when it should request data. * @@ -70,6 +80,15 @@ public final class StatsManager { } /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public boolean removeConfiguration(String configKey) { + // To prevent breakages of old dependencies. + return false; + } + + /** * Remove a configuration from logging. * * @param configKey Configuration key to remove. @@ -93,6 +112,16 @@ public final class StatsManager { } /** + * Temporary to prevent build failures. Will be deleted. + */ + @RequiresPermission(Manifest.permission.DUMP) + public byte[] getData(String configKey) { + // TODO: remove this and all other methods with String-based config keys. + // To prevent build breakages of dependencies. + return null; + } + + /** * Clients can request data with a binder call. This getter is destructive and also clears * the retrieved metrics from statsd memory. * diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index cc4a0b60dd0a..84ae20b92f3c 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -18,30 +18,18 @@ package android.util; import android.os.SystemClock; +import libcore.util.TimeZoneFinder; +import libcore.util.ZoneInfoDB; + import java.io.PrintWriter; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; import java.util.Date; -import java.util.List; -import libcore.util.TimeZoneFinder; -import libcore.util.ZoneInfoDB; - /** * A class containing utility methods related to time zones. */ public class TimeUtils { /** @hide */ public TimeUtils() {} - private static final boolean DBG = false; - private static final String TAG = "TimeUtils"; - - /** Cached results of getTimeZonesWithUniqueOffsets */ - private static final Object sLastUniqueLockObj = new Object(); - private static List<String> sLastUniqueZoneOffsets = null; - private static String sLastUniqueCountry = null; - /** {@hide} */ private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -76,86 +64,6 @@ public class TimeUtils { } /** - * Returns an immutable list of unique time zone IDs for the country. - * - * @param country to find - * @return unmodifiable list of unique time zones, maybe empty but never null. - * @hide - */ - public static List<String> getTimeZoneIdsWithUniqueOffsets(String country) { - synchronized(sLastUniqueLockObj) { - if ((country != null) && country.equals(sLastUniqueCountry)) { - if (DBG) { - Log.d(TAG, "getTimeZonesWithUniqueOffsets(" + - country + "): return cached version"); - } - return sLastUniqueZoneOffsets; - } - } - - Collection<android.icu.util.TimeZone> zones = getIcuTimeZones(country); - ArrayList<android.icu.util.TimeZone> uniqueTimeZones = new ArrayList<>(); - for (android.icu.util.TimeZone zone : zones) { - // See if we already have this offset, - // Using slow but space efficient and these are small. - boolean found = false; - for (int i = 0; i < uniqueTimeZones.size(); i++) { - if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) { - found = true; - break; - } - } - if (!found) { - if (DBG) { - Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" + - zone.getRawOffset() + " zone.getID=" + zone.getID()); - } - uniqueTimeZones.add(zone); - } - } - - synchronized(sLastUniqueLockObj) { - // Cache the last result - sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones); - sLastUniqueCountry = country; - - return sLastUniqueZoneOffsets; - } - } - - private static List<String> extractZoneIds(List<android.icu.util.TimeZone> timeZones) { - List<String> ids = new ArrayList<>(timeZones.size()); - for (android.icu.util.TimeZone timeZone : timeZones) { - ids.add(timeZone.getID()); - } - return Collections.unmodifiableList(ids); - } - - /** - * Returns an immutable list of frozen ICU time zones for the country. - * - * @param countryIso is a two character country code. - * @return TimeZone list, maybe empty but never null. - * @hide - */ - private static List<android.icu.util.TimeZone> getIcuTimeZones(String countryIso) { - if (countryIso == null) { - if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list"); - return Collections.emptyList(); - } - List<android.icu.util.TimeZone> timeZones = - TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso); - if (timeZones == null) { - if (DBG) { - Log.d(TAG, "getIcuTimeZones(" + countryIso - + "): returned null, converting to empty list"); - } - return Collections.emptyList(); - } - return timeZones; - } - - /** * Returns a String indicating the version of the time zone database currently * in use. The format of the string is dependent on the underlying time zone * database implementation, but will typically contain the year in which the database diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 8c7032207c34..bfcf285e970e 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -294,6 +294,11 @@ interface IWindowManager boolean hasNavigationBar(); /** + * Get the position of the nav bar + */ + int getNavBarPosition(); + + /** * Lock the device immediately with the specified options (can be null). */ void lockNow(in Bundle options); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ad71b58514e3..bd3be1b0445e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3226,6 +3226,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000; + /** + * The last aggregated visibility. Used to detect when it truly changes. + */ + private static final int PFLAG3_AGGREGATED_VISIBLE = 0x20000000; + /* End of masks for mPrivateFlags3 */ /** @@ -3387,6 +3392,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * decorations when they are shown. You can perform layout of your inner * UI elements to account for non-fullscreen system UI through the * {@link #fitSystemWindows(Rect)} method. + * + * <p>Note: on displays that have a {@link DisplayCutout}, the window may still be placed + * differently than if {@link #SYSTEM_UI_FLAG_FULLSCREEN} was set, if the + * window's {@link WindowManager.LayoutParams#layoutInDisplayCutoutMode + * layoutInDisplayCutoutMode} is + * {@link WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. To avoid this, use either of the other modes. + * + * @see WindowManager.LayoutParams#layoutInDisplayCutoutMode + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + * @see WindowManager.LayoutParams#LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER */ public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400; @@ -7354,7 +7371,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) { - if (!isShown()) { + // Panes disappearing are relevant even if though the view is no longer visible. + boolean isWindowStateChanged = + (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + boolean isWindowDisappearedEvent = isWindowStateChanged && ((event.getContentChangeTypes() + & AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) != 0); + if (!isShown() && !isWindowDisappearedEvent) { return; } onInitializeAccessibilityEvent(event); @@ -7462,6 +7484,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @hide */ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { + if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) + && !TextUtils.isEmpty(getAccessibilityPaneTitle())) { + event.getText().add(getAccessibilityPaneTitle()); + } } /** @@ -11587,6 +11613,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) { return; } + // Changes to views with a pane title count as window state changes, as the pane title + // marks them as significant parts of the UI. + if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { + final AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + event.setContentChangeTypes(changeType); + onPopulateAccessibilityEvent(event); + if (mParent != null) { + try { + mParent.requestSendAccessibilityEvent(this, event); + } catch (AbstractMethodError e) { + Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() + + " does not fully implement ViewParent", e); + } + } + } + if (mParent != null) { try { mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType); @@ -12520,6 +12563,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @CallSuper public void onVisibilityAggregated(boolean isVisible) { + // Update our internal visibility tracking so we can detect changes + boolean oldVisible = (mPrivateFlags3 & PFLAG3_AGGREGATED_VISIBLE) != 0; + mPrivateFlags3 = isVisible ? (mPrivateFlags3 | PFLAG3_AGGREGATED_VISIBLE) + : (mPrivateFlags3 & ~PFLAG3_AGGREGATED_VISIBLE); if (isVisible && mAttachInfo != null) { initialAwakenScrollBars(); } @@ -12560,6 +12607,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } } + if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) { + if (isVisible != oldVisible) { + notifyAccessibilityStateChanged(isVisible + ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED + : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED); + } + } } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f81a4c33271a..fe3b6964047b 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -20,7 +20,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; @@ -1596,9 +1596,9 @@ public final class ViewRootImpl implements ViewParent, void dispatchApplyInsets(View host) { WindowInsets insets = getWindowInsets(true /* forceConstruct */); - final boolean layoutInCutout = - (mWindowAttributes.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; - if (!layoutInCutout) { + final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode + == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS); + if (!dispatchCutout) { // Window is either not laid out in cutout or the status bar inset takes care of // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy. insets = insets.consumeDisplayCutout(); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index a65aba152c83..ae3c4f09b33a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -889,7 +889,12 @@ public interface WindowManager extends ViewManager { * decorations around the border (such as the status bar). The * window must correctly position its contents to take the screen * decoration into account. This flag is normally set for you - * by Window as described in {@link Window#setFlags}. */ + * by Window as described in {@link Window#setFlags}. + * + * <p>Note: on displays that have a {@link DisplayCutout}, the window may be placed + * such that it avoids the {@link DisplayCutout} area if necessary according to the + * {@link #layoutInDisplayCutoutMode}. + */ public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100; /** Window flag: allow window to extend outside of the screen. */ @@ -1299,26 +1304,11 @@ public interface WindowManager extends ViewManager { @Retention(RetentionPolicy.SOURCE) @LongDef( flag = true, - value = { - LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA, - }) + value = {}) @interface Flags2 {} /** - * Window flag: allow placing the window within the area that overlaps with the - * display cutout. - * - * <p> - * The window must correctly position its contents to take the display cutout into account. - * - * @see DisplayCutout - */ - public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001; - - /** * Various behavioral options/flags. Default is none. - * - * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA */ @Flags2 public long flags2; @@ -2050,6 +2040,77 @@ public interface WindowManager extends ViewManager { */ public boolean hasSystemUiListeners; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = {LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT, + LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS, + LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER}) + @interface LayoutInDisplayCutoutMode {} + + /** + * Controls how the window is laid out if there is a {@link DisplayCutout}. + * + * <p> + * Defaults to {@link #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT}. + * + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + * @see #LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + * @see DisplayCutout + */ + @LayoutInDisplayCutoutMode + public int layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; + + /** + * The window is allowed to extend into the {@link DisplayCutout} area, only if the + * {@link DisplayCutout} is fully contained within the status bar. Otherwise, the window is + * laid out such that it does not overlap with the {@link DisplayCutout} area. + * + * <p> + * In practice, this means that if the window did not set FLAG_FULLSCREEN or + * SYSTEM_UI_FLAG_FULLSCREEN, it can extend into the cutout area in portrait. + * Otherwise (i.e. fullscreen or landscape) it is laid out such that it does overlap the + * cutout area. + * + * <p> + * The usual precautions for not overlapping with the status bar are sufficient for ensuring + * that no important content overlaps with the DisplayCutout. + * + * @see DisplayCutout + * @see WindowInsets + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0; + + /** + * The window is always allowed to extend into the {@link DisplayCutout} area, + * even if fullscreen or in landscape. + * + * <p> + * The window must make sure that no important content overlaps with the + * {@link DisplayCutout}. + * + * @see DisplayCutout + * @see WindowInsets#getDisplayCutout() + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 1; + + /** + * The window is never allowed to overlap with the DisplayCutout area. + * + * <p> + * This should be used with windows that transiently set SYSTEM_UI_FLAG_FULLSCREEN to + * avoid a relayout of the window when the flag is set or cleared. + * + * @see DisplayCutout + * @see View#SYSTEM_UI_FLAG_FULLSCREEN SYSTEM_UI_FLAG_FULLSCREEN + * @see View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + */ + public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2; + + /** * When this window has focus, disable touch pad pointer gesture processing. * The window will receive raw position updates from the touch pad instead @@ -2276,6 +2337,7 @@ public interface WindowManager extends ViewManager { out.writeLong(flags2); out.writeInt(privateFlags); out.writeInt(softInputMode); + out.writeInt(layoutInDisplayCutoutMode); out.writeInt(gravity); out.writeFloat(horizontalMargin); out.writeFloat(verticalMargin); @@ -2332,6 +2394,7 @@ public interface WindowManager extends ViewManager { flags2 = in.readLong(); privateFlags = in.readInt(); softInputMode = in.readInt(); + layoutInDisplayCutoutMode = in.readInt(); gravity = in.readInt(); horizontalMargin = in.readFloat(); verticalMargin = in.readFloat(); @@ -2474,6 +2537,10 @@ public interface WindowManager extends ViewManager { softInputMode = o.softInputMode; changes |= SOFT_INPUT_MODE_CHANGED; } + if (layoutInDisplayCutoutMode != o.layoutInDisplayCutoutMode) { + layoutInDisplayCutoutMode = o.layoutInDisplayCutoutMode; + changes |= LAYOUT_CHANGED; + } if (gravity != o.gravity) { gravity = o.gravity; changes |= LAYOUT_CHANGED; @@ -2651,6 +2718,10 @@ public interface WindowManager extends ViewManager { sb.append(softInputModeToString(softInputMode)); sb.append('}'); } + if (layoutInDisplayCutoutMode != 0) { + sb.append(" layoutInDisplayCutoutMode="); + sb.append(layoutInDisplayCutoutModeToString(layoutInDisplayCutoutMode)); + } sb.append(" ty="); sb.append(ViewDebug.intToString(LayoutParams.class, "type", type)); if (format != PixelFormat.OPAQUE) { @@ -2849,6 +2920,20 @@ public interface WindowManager extends ViewManager { && height == WindowManager.LayoutParams.MATCH_PARENT; } + private static String layoutInDisplayCutoutModeToString( + @LayoutInDisplayCutoutMode int mode) { + switch (mode) { + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT: + return "default"; + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS: + return "always"; + case LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER: + return "never"; + default: + return "unknown(" + mode + ")"; + } + } + private static String softInputModeToString(@SoftInputModeFlags int softInputMode) { final StringBuilder result = new StringBuilder(); final int state = softInputMode & SOFT_INPUT_MASK_STATE; diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 9c1c9e34cfb4..a6f36bbf4ef4 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -45,6 +45,11 @@ public interface WindowManagerPolicyConstants { int PRESENCE_INTERNAL = 1 << 0; int PRESENCE_EXTERNAL = 1 << 1; + // Navigation bar position values + int NAV_BAR_LEFT = 1 << 0; + int NAV_BAR_RIGHT = 1 << 1; + int NAV_BAR_BOTTOM = 1 << 2; + /** * Sticky broadcast of the current HDMI plugged state. */ diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index aa61926dcedc..e0f74a7d6dd6 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -192,9 +192,11 @@ import java.util.List; * <b>TRANSITION TYPES</b></br> * </p> * <p> - * <b>Window state changed</b> - represents the event of opening a - * {@link android.widget.PopupWindow}, {@link android.view.Menu}, - * {@link android.app.Dialog}, etc.</br> + * <b>Window state changed</b> - represents the event of a change to a section of + * the user interface that is visually distinct. Should be sent from either the + * root view of a window or from a view that is marked as a pane + * {@link android.view.View#setAccessibilityPaneTitle(CharSequence)}. Not that changes + * to true windows are represented by {@link #TYPE_WINDOWS_CHANGED}.</br> * <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br> * <em>Properties:</em></br> * <ul> @@ -203,7 +205,7 @@ import java.util.List; * <li>{@link #getClassName()} - The class name of the source.</li> * <li>{@link #getPackageName()} - The package name of the source.</li> * <li>{@link #getEventTime()} - The event time.</li> - * <li>{@link #getText()} - The text of the source's sub-tree.</li> + * <li>{@link #getText()} - The text of the source's sub-tree, including the pane titles.</li> * </ul> * </p> * <p> @@ -436,8 +438,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010; /** - * Represents the event of opening a {@link android.widget.PopupWindow}, - * {@link android.view.Menu}, {@link android.app.Dialog}, etc. + * Represents the event of a change to a visually distinct section of the user interface. + * These events should only be dispatched from {@link android.view.View}s that have + * accessibility pane titles, and replaces {@link #TYPE_WINDOW_CONTENT_CHANGED} for those + * sources. Details about the change are available from {@link #getContentChangeTypes()}. */ public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020; @@ -565,12 +569,30 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; /** - * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: * The node's pane title changed. */ public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008; /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * The node has a pane title, and either just appeared or just was assigned a title when it + * had none before. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 0x00000010; + + /** + * Change type for {@link #TYPE_WINDOW_STATE_CHANGED} event: + * Can mean one of two slightly different things. The primary meaning is that the node has + * a pane title, and was removed from the node hierarchy. It will also be sent if the pane + * title is set to {@code null} after it contained a title. + * No source will be returned if the node is no longer on the screen. To make the change more + * clear for the user, the first entry in {@link #getText()} will return the value that would + * have been returned by {@code getSource().getPaneTitle()}. + */ + public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 0x00000020; + + /** * Change type for {@link #TYPE_WINDOWS_CHANGED} event: * The window was added. */ diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 57f9895f45fa..e5545405728d 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2007-2008 The Android Open Source Project + * Copyright (C) 2007 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 + * 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 + * 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. + * 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.view.inputmethod; @@ -131,13 +131,13 @@ public interface InputConnection { * spans. <strong>Editor authors</strong>: you should strive to * send text with styles if possible, but it is not required. */ - static final int GET_TEXT_WITH_STYLES = 0x0001; + int GET_TEXT_WITH_STYLES = 0x0001; /** * Flag for use with {@link #getExtractedText} to indicate you * would like to receive updates when the extracted text changes. */ - public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001; + int GET_EXTRACTED_TEXT_MONITOR = 0x0001; /** * Get <var>n</var> characters of text before the current cursor @@ -176,7 +176,7 @@ public interface InputConnection { * @return the text before the cursor position; the length of the * returned text might be less than <var>n</var>. */ - public CharSequence getTextBeforeCursor(int n, int flags); + CharSequence getTextBeforeCursor(int n, int flags); /** * Get <var>n</var> characters of text after the current cursor @@ -215,7 +215,7 @@ public interface InputConnection { * @return the text after the cursor position; the length of the * returned text might be less than <var>n</var>. */ - public CharSequence getTextAfterCursor(int n, int flags); + CharSequence getTextAfterCursor(int n, int flags); /** * Gets the selected text, if any. @@ -249,7 +249,7 @@ public interface InputConnection { * later, returns false when the target application does not implement * this method. */ - public CharSequence getSelectedText(int flags); + CharSequence getSelectedText(int flags); /** * Retrieve the current capitalization mode in effect at the @@ -279,7 +279,7 @@ public interface InputConnection { * @return the caps mode flags that are in effect at the current * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}. */ - public int getCursorCapsMode(int reqModes); + int getCursorCapsMode(int reqModes); /** * Retrieve the current text in the input connection's editor, and @@ -314,8 +314,7 @@ public interface InputConnection { * longer valid of the editor can't comply with the request for * some reason. */ - public ExtractedText getExtractedText(ExtractedTextRequest request, - int flags); + ExtractedText getExtractedText(ExtractedTextRequest request, int flags); /** * Delete <var>beforeLength</var> characters of text before the @@ -342,8 +341,8 @@ public interface InputConnection { * delete more characters than are in the editor, as that may have * ill effects on the application. Calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on your service after the batch input is over.</p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on your service after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change @@ -369,7 +368,7 @@ public interface InputConnection { * that range. * @return true on success, false if the input connection is no longer valid. */ - public boolean deleteSurroundingText(int beforeLength, int afterLength); + boolean deleteSurroundingText(int beforeLength, int afterLength); /** * A variant of {@link #deleteSurroundingText(int, int)}. Major differences are: @@ -397,7 +396,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer valid. Returns * {@code false} when the target application does not implement this method. */ - public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); + boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); /** * Replace the currently composing text with the given text, and @@ -416,8 +415,8 @@ public interface InputConnection { * <p>This is usually called by IMEs to add or remove or change * characters in the composing span. Calling this method will * cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over.</p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over.</p> * * <p><strong>Editor authors:</strong> please keep in mind the * text may be very similar or completely different than what was @@ -455,7 +454,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setComposingText(CharSequence text, int newCursorPosition); + boolean setComposingText(CharSequence text, int newCursorPosition); /** * Mark a certain region of text as composing text. If there was a @@ -474,8 +473,8 @@ public interface InputConnection { * <p>Since this does not change the contents of the text, editors should not call * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and * IMEs should not receive - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}. - * </p> + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)}.</p> * * <p>This has no impact on the cursor/selection position. It may * result in the cursor being anywhere inside or outside the @@ -488,7 +487,7 @@ public interface InputConnection { * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the * target application does not implement this method. */ - public boolean setComposingRegion(int start, int end); + boolean setComposingRegion(int start, int end); /** * Have the text editor finish whatever composing text is @@ -507,7 +506,7 @@ public interface InputConnection { * @return true on success, false if the input connection * is no longer valid. */ - public boolean finishComposingText(); + boolean finishComposingText(); /** * Commit text to the text box and set the new cursor position. @@ -522,8 +521,8 @@ public interface InputConnection { * then {@link #finishComposingText()}.</p> * * <p>Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -543,7 +542,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitText(CharSequence text, int newCursorPosition); + boolean commitText(CharSequence text, int newCursorPosition); /** * Commit a completion the user has selected from the possible ones @@ -569,8 +568,8 @@ public interface InputConnection { * * <p>Calling this method (with a valid {@link CompletionInfo} object) * will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -581,15 +580,15 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean commitCompletion(CompletionInfo text); + boolean commitCompletion(CompletionInfo text); /** * Commit a correction automatically performed on the raw user's input. A * typical example would be to correct typos using a dictionary. * * <p>Calling this method will cause the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -601,7 +600,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns false * when the target application does not implement this method. */ - public boolean commitCorrection(CorrectionInfo correctionInfo); + boolean commitCorrection(CorrectionInfo correctionInfo); /** * Set the selection of the text editor. To set the cursor @@ -609,8 +608,8 @@ public interface InputConnection { * * <p>Since this moves the cursor, calling this method will cause * the editor to call - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * on the current IME after the batch input is over. + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} on the current IME after the batch input is over. * <strong>Editor authors</strong>, for this to happen you need to * make the changes known to the input method by calling * {@link InputMethodManager#updateSelection(View, int, int, int, int)}, @@ -628,7 +627,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean setSelection(int start, int end); + boolean setSelection(int start, int end); /** * Have the editor perform an action it has said it can do. @@ -642,7 +641,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean performEditorAction(int editorAction); + boolean performEditorAction(int editorAction); /** * Perform a context menu action on the field. The given id may be one of: @@ -652,7 +651,7 @@ public interface InputConnection { * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, * or {@link android.R.id#switchInputMethod} */ - public boolean performContextMenuAction(int id); + boolean performContextMenuAction(int id); /** * Tell the editor that you are starting a batch of editor @@ -662,8 +661,8 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> use this to avoid getting * calls to - * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)} - * corresponding to intermediate state. Also, use this to avoid + * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, + * int, int)} corresponding to intermediate state. Also, use this to avoid * flickers that may arise from displaying intermediate state. Be * sure to call {@link #endBatchEdit} for each call to this, or * you may block updates in the editor.</p> @@ -678,7 +677,7 @@ public interface InputConnection { * this method starts a batch edit, that means it will always return true * unless the input connection is no longer valid. */ - public boolean beginBatchEdit(); + boolean beginBatchEdit(); /** * Tell the editor that you are done with a batch edit previously @@ -696,7 +695,7 @@ public interface InputConnection { * the latest one (in other words, if the nesting count is > 0), false * otherwise or if the input connection is no longer valid. */ - public boolean endBatchEdit(); + boolean endBatchEdit(); /** * Send a key event to the process that is currently attached @@ -734,7 +733,7 @@ public interface InputConnection { * @see KeyCharacterMap#PREDICTIVE * @see KeyCharacterMap#ALPHA */ - public boolean sendKeyEvent(KeyEvent event); + boolean sendKeyEvent(KeyEvent event); /** * Clear the given meta key pressed states in the given input @@ -749,7 +748,7 @@ public interface InputConnection { * @return true on success, false if the input connection is no longer * valid. */ - public boolean clearMetaKeyStates(int states); + boolean clearMetaKeyStates(int states); /** * Called back when the connected IME switches between fullscreen and normal modes. @@ -766,7 +765,7 @@ public interface InputConnection { * devices. * @see InputMethodManager#isFullscreenMode() */ - public boolean reportFullscreenMode(boolean enabled); + boolean reportFullscreenMode(boolean enabled); /** * API to send private commands from an input method to its @@ -786,7 +785,7 @@ public interface InputConnection { * associated editor understood it), false if the input connection is no longer * valid. */ - public boolean performPrivateCommand(String action, Bundle data); + boolean performPrivateCommand(String action, Bundle data); /** * The editor is requested to call @@ -794,7 +793,7 @@ public interface InputConnection { * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be * used together with {@link #CURSOR_UPDATE_MONITOR}. */ - public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0; + int CURSOR_UPDATE_IMMEDIATE = 1 << 0; /** * The editor is requested to call @@ -805,7 +804,7 @@ public interface InputConnection { * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}. * </p> */ - public static final int CURSOR_UPDATE_MONITOR = 1 << 1; + int CURSOR_UPDATE_MONITOR = 1 << 1; /** * Called by the input method to ask the editor for calling back @@ -821,7 +820,7 @@ public interface InputConnection { * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the * target application does not implement this method. */ - public boolean requestCursorUpdates(int cursorUpdateMode); + boolean requestCursorUpdates(int cursorUpdateMode); /** * Called by the {@link InputMethodManager} to enable application developers to specify a @@ -832,7 +831,7 @@ public interface InputConnection { * * @return {@code null} to use the default {@link Handler}. */ - public Handler getHandler(); + Handler getHandler(); /** * Called by the system up to only once to notify that the system is about to invalidate @@ -846,7 +845,7 @@ public interface InputConnection { * * <p>Note: This does nothing when called from input methods.</p> */ - public void closeConnection(); + void closeConnection(); /** * When this flag is used, the editor will be able to request read access to the content URI @@ -863,7 +862,7 @@ public interface InputConnection { * client is able to request a temporary read-only access even after the current IME is switched * to any other IME as long as the client keeps {@link InputContentInfo} object.</p> **/ - public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = + int INPUT_CONTENT_GRANT_READ_URI_PERMISSION = android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; // 0x00000001 /** @@ -897,6 +896,6 @@ public interface InputConnection { * @return {@code true} if this request is accepted by the application, whether the request * is already handled or still being handled in background, {@code false} otherwise. */ - public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, + boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags, @Nullable Bundle opts); } diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 317730ca092c..f671e22b4922 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -1,17 +1,17 @@ /* - * Copyright (C) 2007-2008 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 - * + * Copyright (C) 2007 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. + * 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.view.inputmethod; @@ -74,6 +74,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextBeforeCursor(int n, int flags) { return mTarget.getTextBeforeCursor(n, flags); } @@ -82,6 +83,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getTextAfterCursor(int n, int flags) { return mTarget.getTextAfterCursor(n, flags); } @@ -90,6 +92,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public CharSequence getSelectedText(int flags) { return mTarget.getSelectedText(flags); } @@ -98,6 +101,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public int getCursorCapsMode(int reqModes) { return mTarget.getCursorCapsMode(reqModes); } @@ -106,6 +110,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { return mTarget.getExtractedText(request, flags); } @@ -114,6 +119,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength); } @@ -122,6 +128,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean deleteSurroundingText(int beforeLength, int afterLength) { return mTarget.deleteSurroundingText(beforeLength, afterLength); } @@ -130,6 +137,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingText(CharSequence text, int newCursorPosition) { return mTarget.setComposingText(text, newCursorPosition); } @@ -138,6 +146,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setComposingRegion(int start, int end) { return mTarget.setComposingRegion(start, end); } @@ -146,6 +155,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean finishComposingText() { return mTarget.finishComposingText(); } @@ -154,6 +164,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitText(CharSequence text, int newCursorPosition) { return mTarget.commitText(text, newCursorPosition); } @@ -162,6 +173,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCompletion(CompletionInfo text) { return mTarget.commitCompletion(text); } @@ -170,6 +182,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitCorrection(CorrectionInfo correctionInfo) { return mTarget.commitCorrection(correctionInfo); } @@ -178,6 +191,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean setSelection(int start, int end) { return mTarget.setSelection(start, end); } @@ -186,6 +200,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performEditorAction(int editorAction) { return mTarget.performEditorAction(editorAction); } @@ -194,6 +209,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performContextMenuAction(int id) { return mTarget.performContextMenuAction(id); } @@ -202,6 +218,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean beginBatchEdit() { return mTarget.beginBatchEdit(); } @@ -210,6 +227,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean endBatchEdit() { return mTarget.endBatchEdit(); } @@ -218,6 +236,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean sendKeyEvent(KeyEvent event) { return mTarget.sendKeyEvent(event); } @@ -226,6 +245,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean clearMetaKeyStates(int states) { return mTarget.clearMetaKeyStates(states); } @@ -234,6 +254,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean reportFullscreenMode(boolean enabled) { return mTarget.reportFullscreenMode(enabled); } @@ -242,6 +263,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean performPrivateCommand(String action, Bundle data) { return mTarget.performPrivateCommand(action, data); } @@ -250,6 +272,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean requestCursorUpdates(int cursorUpdateMode) { return mTarget.requestCursorUpdates(cursorUpdateMode); } @@ -258,6 +281,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public Handler getHandler() { return mTarget.getHandler(); } @@ -266,6 +290,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public void closeConnection() { mTarget.closeConnection(); } @@ -274,6 +299,7 @@ public class InputConnectionWrapper implements InputConnection { * {@inheritDoc} * @throws NullPointerException if the target is {@code null}. */ + @Override public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { return mTarget.commitContent(inputContentInfo, flags, opts); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 1e291209a373..7db5c3207296 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1082,15 +1082,15 @@ public final class InputMethodManager { } /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should only be hidden if it was not explicitly shown + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)} + * to indicate that the soft input window should only be hidden if it was not explicitly shown * by the user. */ public static final int HIDE_IMPLICIT_ONLY = 0x0001; /** - * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft - * input window should normally be hidden, unless it was originally + * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)} + * to indicate that the soft input window should normally be hidden, unless it was originally * shown with {@link #SHOW_FORCED}. */ public static final int HIDE_NOT_ALWAYS = 0x0002; @@ -1869,9 +1869,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #HIDE_IMPLICIT_ONLY}, * {@link #HIDE_NOT_ALWAYS} bit set. - * @deprecated Use {@link InputMethodService#hideSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void hideSoftInputFromInputMethod(IBinder token, int flags) { @@ -1901,9 +1901,9 @@ public final class InputMethodManager { * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} or * {@link #SHOW_FORCED} bit set. - * @deprecated Use {@link InputMethodService#showSoftInputFromInputMethod(int)} - * instead. This method was intended for IME developers who should be accessing APIs through - * the service. APIs in this class are intended for app developers interacting with the IME. + * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was + * intended for IME developers who should be accessing APIs through the service. APIs in this + * class are intended for app developers interacting with the IME. */ @Deprecated public void showSoftInputFromInputMethod(IBinder token, int flags) { diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassifierConstants.java index 51e6168e9aa5..00695b797cb3 100644 --- a/core/java/android/view/textclassifier/TextClassifierConstants.java +++ b/core/java/android/view/textclassifier/TextClassifierConstants.java @@ -45,19 +45,24 @@ public final class TextClassifierConstants { "smart_selection_dark_launch"; private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT = "smart_selection_enabled_for_edit_text"; + private static final String SMART_LINKIFY_ENABLED = + "smart_linkify_enabled"; private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false; private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true; + private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true; /** Default settings. */ static final TextClassifierConstants DEFAULT = new TextClassifierConstants(); private final boolean mDarkLaunch; private final boolean mSuggestSelectionEnabledForEditableText; + private final boolean mSmartLinkifyEnabled; private TextClassifierConstants() { mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT; mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT; + mSmartLinkifyEnabled = SMART_LINKIFY_ENABLED_DEFAULT; } private TextClassifierConstants(@Nullable String settings) { @@ -74,6 +79,9 @@ public final class TextClassifierConstants { mSuggestSelectionEnabledForEditableText = parser.getBoolean( SMART_SELECTION_ENABLED_FOR_EDIT_TEXT, SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT); + mSmartLinkifyEnabled = parser.getBoolean( + SMART_LINKIFY_ENABLED, + SMART_LINKIFY_ENABLED_DEFAULT); } static TextClassifierConstants loadFromString(String settings) { @@ -87,4 +95,8 @@ public final class TextClassifierConstants { public boolean isSuggestSelectionEnabledForEditableText() { return mSuggestSelectionEnabledForEditableText; } + + public boolean isSmartLinkifyEnabled() { + return mSmartLinkifyEnabled; + } } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 9c7be1e97a09..7db0e76d901f 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -186,6 +186,11 @@ final class TextClassifierImpl implements TextClassifier { Utils.validateInput(text); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); + + if (!getSettings().isSmartLinkifyEnabled()) { + return builder.build(); + } + try { final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; final Collection<String> entitiesToIdentify = diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index b3522ec94c0c..e9fe481112a2 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -27,7 +27,6 @@ import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.StrictMode; import android.os.Trace; import android.util.AndroidRuntimeException; import android.util.ArraySet; @@ -251,7 +250,6 @@ public final class WebViewFactory { "WebView.disableWebView() was called: WebView is disabled"); } - StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { Class<WebViewFactoryProvider> providerClass = getProviderClass(); @@ -279,7 +277,6 @@ public final class WebViewFactory { } } finally { Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW); - StrictMode.setThreadPolicy(oldPolicy); } } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index b5ac33070f1f..247c806e928e 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4627,7 +4627,7 @@ public class Editor { return 0; } - protected final void showMagnifier() { + protected final void showMagnifier(@NonNull final MotionEvent event) { if (mMagnifier == null) { return; } @@ -4653,9 +4653,10 @@ public class Editor { final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); - // Horizontally snap to character offset. - final float xPosInView = getHorizontal(mTextView.getLayout(), offset) - + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); + // Horizontally move the magnifier smoothly. + final int[] textViewLocationOnScreen = new int[2]; + mTextView.getLocationOnScreen(textViewLocationOnScreen); + final float xPosInView = event.getRawX() - textViewLocationOnScreen[0]; // Vertically snap to middle of current line. final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f @@ -4850,11 +4851,11 @@ public class Editor { case MotionEvent.ACTION_DOWN: mDownPositionX = ev.getRawX(); mDownPositionY = ev.getRawY(); - showMagnifier(); + showMagnifier(ev); break; case MotionEvent.ACTION_MOVE: - showMagnifier(); + showMagnifier(ev); break; case MotionEvent.ACTION_UP: @@ -5208,11 +5209,11 @@ public class Editor { // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; - showMagnifier(); + showMagnifier(event); break; case MotionEvent.ACTION_MOVE: - showMagnifier(); + showMagnifier(event); break; case MotionEvent.ACTION_UP: diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 26dfcc2d668a..310b1708cb13 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -32,6 +32,7 @@ import android.view.PixelCopy; import android.view.Surface; import android.view.SurfaceView; import android.view.View; +import android.view.ViewParent; import com.android.internal.util.Preconditions; @@ -44,6 +45,8 @@ public final class Magnifier { private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1; // The view to which this magnifier is attached. private final View mView; + // The coordinates of the view in the surface. + private final int[] mViewCoordinatesInSurface; // The window containing the magnifier. private final PopupWindow mWindow; // The center coordinates of the window containing the magnifier. @@ -87,6 +90,8 @@ public final class Magnifier { com.android.internal.R.dimen.magnifier_height); mZoomScale = context.getResources().getFloat( com.android.internal.R.dimen.magnifier_zoom_scale); + // The view's surface coordinates will not be updated until the magnifier is first shown. + mViewCoordinatesInSurface = new int[2]; mWindow = new PopupWindow(context); mWindow.setContentView(content); @@ -120,9 +125,34 @@ public final class Magnifier { configureCoordinates(xPosInView, yPosInView); // Clamp startX value to avoid distorting the rendering of the magnifier content. - final int startX = Math.max(0, Math.min( + // For this, we compute: + // - zeroScrollXInSurface: this is the start x of mView, where this is not masked by a + // potential scrolling container. For example, if mView is a + // TextView contained in a HorizontalScrollView, + // mViewCoordinatesInSurface will reflect the surface position of + // the first text character, rather than the position of the first + // visible one. Therefore, we need to add back the amount of + // scrolling from the parent containers. + // - actualWidth: similarly, the width of a View will be larger than its actually visible + // width when it is contained in a scrolling container. We need to use + // the minimum width of a scrolling container which contains this view. + int zeroScrollXInSurface = mViewCoordinatesInSurface[0]; + int actualWidth = mView.getWidth(); + ViewParent viewParent = mView.getParent(); + while (viewParent instanceof View) { + final View container = (View) viewParent; + if (container.canScrollHorizontally(-1 /* left scroll */) + || container.canScrollHorizontally(1 /* right scroll */)) { + zeroScrollXInSurface += container.getScrollX(); + actualWidth = Math.min(actualWidth, container.getWidth() + - container.getPaddingLeft() - container.getPaddingRight()); + } + viewParent = viewParent.getParent(); + } + + final int startX = Math.max(zeroScrollXInSurface, Math.min( mCenterZoomCoords.x - mBitmap.getWidth() / 2, - mView.getWidth() - mBitmap.getWidth())); + zeroScrollXInSurface + actualWidth - mBitmap.getWidth())); final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) { @@ -169,10 +199,9 @@ public final class Magnifier { posX = xPosInView; posY = yPosInView; } else { - final int[] coordinatesInSurface = new int[2]; - mView.getLocationInSurface(coordinatesInSurface); - posX = xPosInView + coordinatesInSurface[0]; - posY = yPosInView + coordinatesInSurface[1]; + mView.getLocationInSurface(mViewCoordinatesInSurface); + posX = xPosInView + mViewCoordinatesInSurface[0]; + posY = yPosInView + mViewCoordinatesInSurface[1]; } mCenterZoomCoords.x = Math.round(posX); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 716b205380c2..8c4e422d457b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -77,8 +77,8 @@ import android.text.GraphicsOperations; import android.text.InputFilter; import android.text.InputType; import android.text.Layout; +import android.text.MeasuredText; import android.text.ParcelableSpan; -import android.text.PremeasuredText; import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; @@ -5393,7 +5393,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); - } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) { + } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) { text = TextUtils.stringOrSpannedString(text); } diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java new file mode 100644 index 000000000000..310a7bbdcabf --- /dev/null +++ b/core/java/android/widget/VideoView2.java @@ -0,0 +1,363 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Canvas; +import android.media.AudioAttributes; +import android.media.MediaPlayer; +import android.media.update.ApiLoader; +import android.media.update.VideoView2Provider; +import android.media.update.ViewProvider; +import android.net.Uri; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; + +/** + * TODO PUBLIC API + * @hide + */ +public class VideoView2 extends FrameLayout { + @IntDef({ + VIEW_TYPE_TEXTUREVIEW, + VIEW_TYPE_SURFACEVIEW + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ViewType {} + public static final int VIEW_TYPE_SURFACEVIEW = 1; + public static final int VIEW_TYPE_TEXTUREVIEW = 2; + + private final VideoView2Provider mProvider; + + /** + * @hide + */ + public VideoView2(@NonNull Context context) { + this(context, null); + } + + /** + * @hide + */ + public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * @hide + */ + public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + /** + * @hide + */ + public VideoView2( + @NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider()); + } + + /** + * @hide + */ + public VideoView2Provider getProvider() { + return mProvider; + } + + /** + * @hide + */ + public void start() { + mProvider.start_impl(); + } + + /** + * @hide + */ + public void pause() { + mProvider.pause_impl(); + } + + /** + * @hide + */ + public int getDuration() { + return mProvider.getDuration_impl(); + } + + /** + * @hide + */ + public int getCurrentPosition() { + return mProvider.getCurrentPosition_impl(); + } + + /** + * @hide + */ + public void seekTo(int msec) { + mProvider.seekTo_impl(msec); + } + + /** + * @hide + */ + public boolean isPlaying() { + return mProvider.isPlaying_impl(); + } + + /** + * @hide + */ + public int getBufferPercentage() { + return mProvider.getBufferPercentage_impl(); + } + + /** + * @hide + */ + public int getAudioSessionId() { + return mProvider.getAudioSessionId_impl(); + } + + /** + * @hide + */ + public void showSubtitle() { + mProvider.showSubtitle_impl(); + } + + /** + * @hide + */ + public void hideSubtitle() { + mProvider.hideSubtitle_impl(); + } + + /** + * @hide + */ + public void setAudioFocusRequest(int focusGain) { + mProvider.setAudioFocusRequest_impl(focusGain); + } + + /** + * @hide + */ + public void setAudioAttributes(@NonNull AudioAttributes attributes) { + mProvider.setAudioAttributes_impl(attributes); + } + + /** + * @hide + */ + public void setVideoPath(String path) { + mProvider.setVideoPath_impl(path); + } + + /** + * @hide + */ + public void setVideoURI(Uri uri) { + mProvider.setVideoURI_impl(uri); + } + + /** + * @hide + */ + public void setVideoURI(Uri uri, Map<String, String> headers) { + mProvider.setVideoURI_impl(uri, headers); + } + + /** + * @hide + */ + public void setMediaController2(MediaController2 controllerView) { + mProvider.setMediaController2_impl(controllerView); + } + + /** + * @hide + */ + public void setViewType(@ViewType int viewType) { + mProvider.setViewType_impl(viewType); + } + + /** + * @hide + */ + @ViewType + public int getViewType() { + return mProvider.getViewType_impl(); + } + + /** + * @hide + */ + public void stopPlayback() { + mProvider.stopPlayback_impl(); + } + + /** + * @hide + */ + public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) { + mProvider.setOnPreparedListener_impl(l); + } + + /** + * @hide + */ + public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) { + mProvider.setOnCompletionListener_impl(l); + } + + /** + * @hide + */ + public void setOnErrorListener(MediaPlayer.OnErrorListener l) { + mProvider.setOnErrorListener_impl(l); + } + + /** + * @hide + */ + public void setOnInfoListener(MediaPlayer.OnInfoListener l) { + mProvider.setOnInfoListener_impl(l); + } + + /** + * @hide + */ + public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) { + mProvider.setOnViewTypeChangedListener_impl(l); + } + + /** + * @hide + */ + public interface OnViewTypeChangedListener { + /** + * @hide + */ + void onViewTypeChanged(@ViewType int viewType); + } + + @Override + public CharSequence getAccessibilityClassName() { + return mProvider.getAccessibilityClassName_impl(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mProvider.onTouchEvent_impl(ev); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + return mProvider.onTrackballEvent_impl(ev); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mProvider.onKeyDown_impl(keyCode, event); + } + + @Override + public void onFinishInflate() { + mProvider.onFinishInflate_impl(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + return mProvider.dispatchKeyEvent_impl(event); + } + + @Override + public void setEnabled(boolean enabled) { + mProvider.setEnabled_impl(enabled); + } + + private class SuperProvider implements ViewProvider { + @Override + public void onAttachedToWindow_impl() { + VideoView2.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + VideoView2.super.onDetachedFromWindow(); + } + + @Override + public void onLayout_impl(boolean changed, int left, int top, int right, int bottom) { + VideoView2.super.onLayout(changed, left, top, right, bottom); + } + + @Override + public void draw_impl(Canvas canvas) { + VideoView2.super.draw(canvas); + } + + @Override + public CharSequence getAccessibilityClassName_impl() { + return VideoView2.super.getAccessibilityClassName(); + } + + @Override + public boolean onTouchEvent_impl(MotionEvent ev) { + return VideoView2.super.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent_impl(MotionEvent ev) { + return VideoView2.super.onTrackballEvent(ev); + } + + @Override + public boolean onKeyDown_impl(int keyCode, KeyEvent event) { + return VideoView2.super.onKeyDown(keyCode, event); + } + + @Override + public void onFinishInflate_impl() { + VideoView2.super.onFinishInflate(); + } + + @Override + public boolean dispatchKeyEvent_impl(KeyEvent event) { + return VideoView2.super.dispatchKeyEvent(event); + } + + @Override + public void setEnabled_impl(boolean enabled) { + VideoView2.super.setEnabled(enabled); + } + } +} diff --git a/core/java/com/android/internal/policy/KeyguardDismissCallback.java b/core/java/com/android/internal/policy/KeyguardDismissCallback.java new file mode 100644 index 000000000000..38337ec6f274 --- /dev/null +++ b/core/java/com/android/internal/policy/KeyguardDismissCallback.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.policy; + +import android.os.RemoteException; +import com.android.internal.policy.IKeyguardDismissCallback; + +/** + * @hide + */ +public class KeyguardDismissCallback extends IKeyguardDismissCallback.Stub { + + @Override + public void onDismissError() throws RemoteException { + // To be overidden + } + + @Override + public void onDismissSucceeded() throws RemoteException { + // To be overidden + } + + @Override + public void onDismissCancelled() throws RemoteException { + // To be overidden + } +} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b3f66e9652f6..96f3308ec178 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -84,7 +84,7 @@ cc_library_shared { "android_view_VelocityTracker.cpp", "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", - "android_text_MeasuredText.cpp", + "android_text_MeasuredParagraph.cpp", "android_text_StaticLayout.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6d7fe056acdf..6569b4783e67 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -178,7 +178,7 @@ extern int register_android_net_LocalSocketImpl(JNIEnv* env); extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); -extern int register_android_text_MeasuredText(JNIEnv* env); +extern int register_android_text_MeasuredParagraph(JNIEnv* env); extern int register_android_text_StaticLayout(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); @@ -1342,7 +1342,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_content_XmlBlock), REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), - REG_JNI(register_android_text_MeasuredText), + REG_JNI(register_android_text_MeasuredParagraph), REG_JNI(register_android_text_StaticLayout), REG_JNI(register_android_view_InputDevice), REG_JNI(register_android_view_KeyCharacterMap), diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredParagraph.cpp index af9d13122201..bdae0b22987f 100644 --- a/core/jni/android_text_MeasuredText.cpp +++ b/core/jni/android_text_MeasuredParagraph.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "MeasuredText" +#define LOG_TAG "MeasuredParagraph" #include "ScopedIcuLocale.h" #include "unicode/locid.h" @@ -49,7 +49,7 @@ static inline Paint* toPaint(jlong ptr) { return reinterpret_cast<Paint*>(ptr); } -static inline minikin::MeasuredText* toMeasuredText(jlong ptr) { +static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) { return reinterpret_cast<minikin::MeasuredText*>(ptr); } @@ -57,8 +57,8 @@ template<typename Ptr> static inline jlong toJLong(Ptr ptr) { return reinterpret_cast<jlong>(ptr); } -static void releaseMeasuredText(jlong measuredTextPtr) { - delete toMeasuredText(measuredTextPtr); +static void releaseMeasuredParagraph(jlong measuredTextPtr) { + delete toMeasuredParagraph(measuredTextPtr); } // Regular JNI @@ -84,7 +84,7 @@ static void nAddReplacementRun(JNIEnv* /* unused */, jclass /* unused */, jlong } // Regular JNI -static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, +static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr, jcharArray javaText) { ScopedCharArrayRO text(env, javaText); const minikin::U16StringPiece textBuffer(text.get(), text.size()); @@ -100,23 +100,23 @@ static void nFreeBuilder(JNIEnv* env, jclass /* unused */, jlong builderPtr) { // CriticalNative static jlong nGetReleaseFunc() { - return toJLong(&releaseMeasuredText); + return toJLong(&releaseMeasuredParagraph); } static const JNINativeMethod gMethods[] = { - // MeasuredTextBuilder native functions. + // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, - {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText}, + {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, - // MeasuredText native functions. + // MeasuredParagraph native functions. {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives }; -int register_android_text_MeasuredText(JNIEnv* env) { - return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods)); +int register_android_text_MeasuredParagraph(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods)); } } diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp index b5c23dfa873d..682dc8739869 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_StaticLayout.cpp @@ -174,7 +174,7 @@ static const JNINativeMethod gMethods[] = { // Inputs "[C" // text - "J" // MeasuredText ptr. + "J" // MeasuredParagraph ptr. "I" // length "F" // firstWidth "I" // firstWidthLineCount diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f0cf5b84687d..a3e8f1ebd3ff 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2953,13 +2953,14 @@ <!-- Allows an application to collect usage infomation about brightness slider changes. <p>Not for use by third-party applications.</p> - TODO: make a System API - @hide --> + @hide + @SystemApi --> <permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|development" /> <!-- Allows an application to modify the display brightness configuration - @hide --> + @hide + @SystemApi --> <permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS" android:protectionLevel="signature|privileged|development" /> diff --git a/core/res/res/raw/color_fade_frag.frag b/core/res/res/raw/color_fade_frag.frag index a66a5a761a1f..29975d5f7b5e 100644 --- a/core/res/res/raw/color_fade_frag.frag +++ b/core/res/res/raw/color_fade_frag.frag @@ -3,40 +3,12 @@ precision mediump float; uniform samplerExternalOES texUnit; uniform float opacity; -uniform float saturation; uniform float gamma; varying vec2 UV; -vec3 rgb2hsl(vec3 rgb) -{ - float e = 1.0e-7; - - vec4 p = rgb.g < rgb.b ? vec4(rgb.bg, -1.0, 2.0 / 3.0) : vec4(rgb.gb, 0.0, -1.0 / 3.0); - vec4 q = rgb.r < p.x ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx); - - float v = q.x; - float c = v - min(q.w, q.y); - float h = abs((q.w - q.y) / (6.0 * c + e) + q.z); - float l = v - c * 0.5; - float s = c / (1.0 - abs(2.0 * l - 1.0) + e); - return clamp(vec3(h, s, l), 0.0, 1.0); -} - -vec3 hsl2rgb(vec3 hsl) -{ - vec3 h = vec3(hsl.x * 6.0); - vec3 p = abs(h - vec3(3.0, 2.0, 4.0)); - vec3 q = 2.0 - p; - - vec3 rgb = clamp(vec3(p.x - 1.0, q.yz), 0.0, 1.0); - float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y; - return (rgb - vec3(0.5)) * c + hsl.z; -} - void main() { vec4 color = texture2D(texUnit, UV); - vec3 hsl = rgb2hsl(color.xyz); - vec3 rgb = pow(hsl2rgb(vec3(hsl.x, hsl.y * saturation, hsl.z * opacity)), vec3(gamma)); + vec3 rgb = pow(color.rgb * opacity, vec3(gamma)); gl_FragColor = vec4(rgb, 1.0); } diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index efbe9c2566fe..287f29615e2b 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2568,9 +2568,9 @@ e.g. name=ro.oem.sku value=MKT210. Overlay will be ignored unless system property exists and is set to specified value --> - <!-- @hide @SystemApi This shouldn't be public. --> + <!-- @hide This shouldn't be public. --> <attr name="requiredSystemPropertyName" format="string" /> - <!-- @hide @SystemApi This shouldn't be public. --> + <!-- @hide This shouldn't be public. --> <attr name="requiredSystemPropertyValue" format="string" /> </declare-styleable> diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index 04131009b141..f8a77f8f1c32 100644 --- a/core/res/res/values/colors_material.xml +++ b/core/res/res/values/colors_material.xml @@ -77,9 +77,9 @@ <item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item> <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item> - <item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item> - <item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item> - <item name="highlight_alpha_material_colored" format="float" type="dimen">0.26</item> + <item name="highlight_alpha_material_light" format="float" type="dimen">0.16</item> + <item name="highlight_alpha_material_dark" format="float" type="dimen">0.32</item> + <item name="highlight_alpha_material_colored" format="float" type="dimen">0.48</item> <!-- Primary & accent colors --> <eat-comment /> diff --git a/core/tests/coretests/src/android/text/MeasuredTextTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index ddef0c651b31..5d33397e13f2 100644 --- a/core/tests/coretests/src/android/text/MeasuredTextTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -31,7 +31,7 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) -public class MeasuredTextTest { +public class MeasuredParagraphTest { private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR; private static final TextDirectionHeuristic RTL = TextDirectionHeuristics.RTL; @@ -60,9 +60,9 @@ public class MeasuredTextTest { @Test public void buildForBidi() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForBidi("XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForBidi("XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -75,7 +75,7 @@ public class MeasuredTextTest { assertEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForBidi("_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -91,9 +91,9 @@ public class MeasuredTextTest { @Test public void buildForMeasurement() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -109,7 +109,8 @@ public class MeasuredTextTest { assertEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = + MeasuredParagraph.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -129,9 +130,9 @@ public class MeasuredTextTest { @Test public void buildForStaticLayout() { - MeasuredText mt = null; + MeasuredParagraph mt = null; - mt = MeasuredText.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null); + mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null); assertNotNull(mt); assertNotNull(mt.getChars()); assertEquals("XXX", charsToString(mt.getChars())); @@ -145,7 +146,8 @@ public class MeasuredTextTest { assertNotEquals(0, mt.getNativePtr()); // Recycle it - MeasuredText mt2 = MeasuredText.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt); + MeasuredParagraph mt2 = + MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt); assertEquals(mt2, mt); assertNotNull(mt2.getChars()); assertEquals("VVV", charsToString(mt.getChars())); @@ -163,6 +165,6 @@ public class MeasuredTextTest { @Test public void testFor70146381() { - MeasuredText.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null); + MeasuredParagraph.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null); } } diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java index dea194e4ffde..4571553d4d4e 100644 --- a/graphics/java/android/graphics/drawable/RippleBackground.java +++ b/graphics/java/android/graphics/drawable/RippleBackground.java @@ -16,17 +16,12 @@ package android.graphics.drawable; -import android.animation.Animator; -import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.graphics.Canvas; -import android.graphics.CanvasProperty; import android.graphics.Paint; import android.graphics.Rect; import android.util.FloatProperty; -import android.view.DisplayListCanvas; -import android.view.RenderNodeAnimator; import android.view.animation.LinearInterpolator; /** @@ -78,8 +73,8 @@ class RippleBackground extends RippleComponent { private void onStateChanged(boolean animateChanged) { float newOpacity = 0.0f; - if (mHovered) newOpacity += 1.0f; - if (mFocused) newOpacity += 1.0f; + if (mHovered) newOpacity += .25f; + if (mFocused) newOpacity += .75f; if (mAnimator != null) { mAnimator.cancel(); mAnimator = null; diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java index 734cff542c51..b883656d784a 100644 --- a/graphics/java/android/graphics/drawable/RippleDrawable.java +++ b/graphics/java/android/graphics/drawable/RippleDrawable.java @@ -264,8 +264,8 @@ public class RippleDrawable extends LayerDrawable { } setRippleActive(enabled && pressed); - setBackgroundActive(hovered, focused); + return changed; } @@ -879,22 +879,18 @@ public class RippleDrawable extends LayerDrawable { // Grab the color for the current state and cut the alpha channel in // half so that the ripple and background together yield full alpha. final int color = mState.mColor.getColorForState(getState(), Color.BLACK); - final int halfAlpha = (Color.alpha(color) / 2) << 24; final Paint p = mRipplePaint; if (mMaskColorFilter != null) { // The ripple timing depends on the paint's alpha value, so we need // to push just the alpha channel into the paint and let the filter // handle the full-alpha color. - final int fullAlphaColor = color | (0xFF << 24); - mMaskColorFilter.setColor(fullAlphaColor); - - p.setColor(halfAlpha); + mMaskColorFilter.setColor(color | 0xFF000000); + p.setColor(color & 0xFF000000); p.setColorFilter(mMaskColorFilter); p.setShader(mMaskShader); } else { - final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha; - p.setColor(halfAlphaColor); + p.setColor(color); p.setColorFilter(null); p.setShader(null); } diff --git a/media/java/android/media/update/ApiLoader.java b/media/java/android/media/update/ApiLoader.java index b57e02d559e0..07483f60c69e 100644 --- a/media/java/android/media/update/ApiLoader.java +++ b/media/java/android/media/update/ApiLoader.java @@ -49,8 +49,8 @@ public final class ApiLoader { Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); sMediaLibrary = libContext.getClassLoader() .loadClass(UPDATE_CLASS) - .getMethod(UPDATE_METHOD, Context.class) - .invoke(null, appContext); + .getMethod(UPDATE_METHOD, Context.class, Context.class) + .invoke(null, appContext, libContext); return sMediaLibrary; } } diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 19f01c2bcc7f..a1e2404ca78a 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -18,6 +18,7 @@ package android.media.update; import android.annotation.SystemApi; import android.widget.MediaController2; +import android.widget.VideoView2; /** * Interface for connecting the public API to an updatable implementation. @@ -31,4 +32,5 @@ import android.widget.MediaController2; public interface StaticProvider { MediaController2Provider createMediaController2( MediaController2 instance, ViewProvider superProvider); + VideoView2Provider createVideoView2(VideoView2 instance, ViewProvider superProvider); } diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java new file mode 100644 index 000000000000..6fc9bdc64e02 --- /dev/null +++ b/media/java/android/media/update/VideoView2Provider.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018 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.media.update; + +import android.media.AudioAttributes; +import android.media.MediaPlayer; +import android.net.Uri; +import android.widget.MediaController2; +import android.widget.VideoView2; + +import java.util.Map; + +/** + * Interface for connecting the public API to an updatable implementation. + * + * Each instance object is connected to one corresponding updatable object which implements the + * runtime behavior of that class. There should a corresponding provider method for all public + * methods. + * + * All methods behave as per their namesake in the public API. + * + * @see android.widget.VideoView2 + * + * @hide + */ +// TODO @SystemApi +public interface VideoView2Provider extends ViewProvider { + void start_impl(); + void pause_impl(); + int getDuration_impl(); + int getCurrentPosition_impl(); + void seekTo_impl(int msec); + boolean isPlaying_impl(); + int getBufferPercentage_impl(); + int getAudioSessionId_impl(); + void showSubtitle_impl(); + void hideSubtitle_impl(); + void setAudioFocusRequest_impl(int focusGain); + void setAudioAttributes_impl(AudioAttributes attributes); + void setVideoPath_impl(String path); + void setVideoURI_impl(Uri uri); + void setVideoURI_impl(Uri uri, Map<String, String> headers); + void setMediaController2_impl(MediaController2 controllerView); + void setViewType_impl(int viewType); + int getViewType_impl(); + void stopPlayback_impl(); + void setOnPreparedListener_impl(MediaPlayer.OnPreparedListener l); + void setOnCompletionListener_impl(MediaPlayer.OnCompletionListener l); + void setOnErrorListener_impl(MediaPlayer.OnErrorListener l); + void setOnInfoListener_impl(MediaPlayer.OnInfoListener l); + void setOnViewTypeChangedListener_impl(VideoView2.OnViewTypeChangedListener l); +} diff --git a/native/android/net.c b/native/android/net.c index de4b90cc1956..60296a7bd00c 100644 --- a/native/android/net.c +++ b/native/android/net.c @@ -27,7 +27,7 @@ static int getnetidfromhandle(net_handle_t handle, unsigned *netid) { static const uint32_t k32BitMask = 0xffffffff; // This value MUST be kept in sync with the corresponding value in // the android.net.Network#getNetworkHandle() implementation. - static const uint32_t kHandleMagic = 0xfacade; + static const uint32_t kHandleMagic = 0xcafed00d; // Check for minimum acceptable version of the API in the low bits. if (handle != NETWORK_UNSPECIFIED && diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java index adb4832adcff..ae24c079e37b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java @@ -16,9 +16,9 @@ package com.android.settingslib.core.lifecycle; import static android.arch.lifecycle.Lifecycle.Event.ON_START; - import static com.google.common.truth.Truth.assertThat; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.view.Menu; import android.view.MenuInflater; @@ -48,6 +48,7 @@ import org.robolectric.annotation.Config; @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class LifecycleTest { + private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; public static class TestDialogFragment extends ObservableDialogFragment { @@ -146,7 +147,8 @@ public class LifecycleTest { @Before public void setUp() { - mLifecycle = new Lifecycle(() -> mLifecycle); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java index 5d5733e463ea..050877d3ebfe 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogpersistPreferenceControllerTest.java @@ -17,11 +17,11 @@ package com.android.settingslib.development; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; +import android.arch.lifecycle.LifecycleOwner; import android.os.SystemProperties; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; @@ -44,6 +44,7 @@ import org.robolectric.annotation.Config; shadows = SystemPropertiesTestImpl.class) public class LogpersistPreferenceControllerTest { + private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; @Mock @@ -57,7 +58,8 @@ public class LogpersistPreferenceControllerTest { public void setUp() { MockitoAnnotations.initMocks(this); SystemProperties.set("ro.debuggable", "1"); - mLifecycle = new Lifecycle(() -> mLifecycle); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); mController = new AbstractLogpersistPreferenceController(RuntimeEnvironment.application, mLifecycle) { @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java index 75b6c5fcefc1..88c57b50f87e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java @@ -17,13 +17,13 @@ package com.android.settingslib.widget; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.arch.lifecycle.LifecycleOwner; import android.support.v14.preference.PreferenceFragment; import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceScreen; @@ -49,13 +49,15 @@ public class FooterPreferenceMixinTest { @Mock private PreferenceScreen mScreen; + private LifecycleOwner mLifecycleOwner; private Lifecycle mLifecycle; private FooterPreferenceMixin mMixin; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mLifecycle = new Lifecycle(() -> mLifecycle); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class)); when(mFragment.getPreferenceManager().getContext()) .thenReturn(ShadowApplication.getInstance().getApplicationContext()); diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 1be064564574..48a3a3084ca0 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -28,7 +28,7 @@ <string name="def_bluetooth_disabled_profiles" translatable="false">0</string> <bool name="def_auto_time">true</bool> <bool name="def_auto_time_zone">true</bool> - <bool name="def_accelerometer_rotation">true</bool> + <bool name="def_accelerometer_rotation">false</bool> <!-- Default screen brightness, from 0 to 255. 102 is 40%. --> <integer name="def_screen_brightness">102</integer> <bool name="def_screen_brightness_automatic_mode">false</bool> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 1167d69a1577..175cff6b61b2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1584,6 +1584,11 @@ public class SettingsProvider extends ContentProvider { restriction = UserManager.DISALLOW_SAFE_BOOT; break; + case Settings.Global.AIRPLANE_MODE_ON: + if ("0".equals(value)) return false; + restriction = UserManager.DISALLOW_AIRPLANE_MODE; + break; + default: if (setting != null && setting.startsWith(Settings.Global.DATA_ROAMING)) { if ("0".equals(value)) return false; @@ -2940,7 +2945,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 150; + private static final int SETTINGS_VERSION = 151; private final int mUserId; @@ -3533,6 +3538,18 @@ public class SettingsProvider extends ContentProvider { currentVersion = 150; } + if (currentVersion == 150) { + // Version 151: Reset rotate locked setting for upgrading users + final SettingsState systemSettings = getSystemSettingsLocked(userId); + systemSettings.insertSettingLocked( + Settings.System.ACCELEROMETER_ROTATION, + getContext().getResources().getBoolean( + R.bool.def_accelerometer_rotation) ? "1" : "0", + null, true, SettingsState.SYSTEM_PACKAGE_NAME); + + currentVersion = 151; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java index 56a3ee3a28f7..e25930c18947 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java @@ -50,5 +50,7 @@ public interface NavBarButtonProvider extends Plugin { } void setDarkIntensity(float intensity); + + void setDelayTouchFeedback(boolean shouldDelay); } } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java index 674ed5a3a480..6131acc91ca5 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavGesture.java @@ -14,6 +14,7 @@ package com.android.systemui.plugins.statusbar.phone; +import android.graphics.Canvas; import android.view.MotionEvent; import com.android.systemui.plugins.Plugin; @@ -35,6 +36,12 @@ public interface NavGesture extends Plugin { public void setBarState(boolean vertical, boolean isRtl); + public void onDraw(Canvas canvas); + + public void onDarkIntensityChange(float intensity); + + public void onLayout(boolean changed, int left, int top, int right, int bottom); + public default void destroy() { } } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml index c97cfc4bb835..9adb5501345d 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml @@ -34,7 +34,6 @@ android:orientation="vertical"> <RelativeLayout android:id="@+id/keyguard_clock_container" - android:animateLayoutChanges="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|top"> @@ -51,16 +50,6 @@ android:format12Hour="@string/keyguard_widget_12_hours_format" android:format24Hour="@string/keyguard_widget_24_hours_format" android:layout_marginBottom="@dimen/bottom_text_spacing_digital" /> - <com.android.systemui.ChargingView - android:id="@+id/battery_doze" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignTop="@id/clock_view" - android:layout_alignBottom="@id/clock_view" - android:layout_toEndOf="@id/clock_view" - android:visibility="invisible" - android:src="@drawable/ic_aod_charging_24dp" - android:contentDescription="@string/accessibility_ambient_display_charging" /> <View android:id="@+id/clock_separator" android:layout_width="16dp" diff --git a/packages/SystemUI/res/drawable/ic_face_unlock.xml b/packages/SystemUI/res/drawable/ic_face_unlock.xml new file mode 100644 index 000000000000..29c22759be70 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_face_unlock.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright (C) 2018 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="48dp" + android:height="48dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path android:fillColor="?attr/wallpaperTextColor" + android:strokeColor="?attr/wallpaperTextColor" + android:strokeWidth="1" + android:pathData="M9,11.75C8.31,11.75 7.75,12.31 7.75,13C7.75,13.69 8.31,14.25 9,14.25C9.69,14.25 10.25,13.69 10.25,13C10.25,12.31 9.69,11.75 9,11.75ZM15,11.75C14.31,11.75 13.75,12.31 13.75,13C13.75,13.69 14.31,14.25 15,14.25C15.69,14.25 16.25,13.69 16.25,13C16.25,12.31 15.69,11.75 15,11.75ZM12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2ZM12,20C7.59,20 4,16.41 4,12C4,11.71 4.02,11.42 4.05,11.14C6.41,10.09 8.28,8.16 9.26,5.77C11.07,8.33 14.05,10 17.42,10C18.2,10 18.95,9.91 19.67,9.74C19.88,10.45 20,11.21 20,12C20,16.41 16.41,20 12,20Z" + /> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_background_primary.xml b/packages/SystemUI/res/drawable/qs_background_primary.xml index 03bba53946da..dd74cadd0955 100644 --- a/packages/SystemUI/res/drawable/qs_background_primary.xml +++ b/packages/SystemUI/res/drawable/qs_background_primary.xml @@ -16,5 +16,6 @@ <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> <solid android:color="@color/qs_background_dark"/> + <corners android:radius="?android:attr/dialogCornerRadius" /> </shape> </inset> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 2fd4df4d1cc7..d0d379c286bd 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -40,6 +40,7 @@ android:gravity="center_horizontal" android:textStyle="italic" android:textColor="?attr/wallpaperTextColorSecondary" + android:textSize="16sp" android:textAppearance="?android:attr/textAppearanceSmall" android:visibility="gone" /> @@ -50,6 +51,7 @@ android:gravity="center_horizontal" android:textStyle="italic" android:textColor="?attr/wallpaperTextColorSecondary" + android:textSize="16sp" android:textAppearance="?android:attr/textAppearanceSmall" android:accessibilityLiveRegion="polite" /> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index bef0830d5802..c5e5ee1f4af0 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -43,6 +43,8 @@ android:layout_width="@dimen/qs_panel_width" android:layout_height="match_parent" android:layout_gravity="@integer/notification_panel_layout_gravity" + android:layout_marginStart="@dimen/notification_side_paddings" + android:layout_marginEnd="@dimen/notification_side_paddings" android:clipToPadding="false" android:clipChildren="false" systemui:viewType="com.android.systemui.plugins.qs.QS" /> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index f244d88b8573..f5be337a7d4f 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -143,6 +143,9 @@ <color name="remote_input_accent">#eeeeee</color> + <color name="quick_step_track_background_dark">#61000000</color> + <color name="quick_step_track_background_light">#4DFFFFFF</color> + <!-- Keyboard shortcuts colors --> <color name="ksh_application_group_color">#fff44336</color> <color name="ksh_keyword_color">#d9000000</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a510c4a6c503..39ed08eae55a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -579,6 +579,7 @@ <dimen name="keyguard_affordance_icon_width">24dp</dimen> <dimen name="keyguard_indication_margin_bottom">65dp</dimen> + <dimen name="keyguard_indication_margin_bottom_ambient">30dp</dimen> <!-- The text size for battery level --> <dimen name="battery_level_text_size">12sp</dimen> @@ -870,6 +871,8 @@ <dimen name="rounded_corner_radius">0dp</dimen> <dimen name="rounded_corner_content_padding">0dp</dimen> <dimen name="nav_content_padding">0dp</dimen> + <dimen name="nav_quick_scrub_track_edge_padding">32dp</dimen> + <dimen name="nav_quick_scrub_track_thickness">2dp</dimen> <!-- Intended corner radius when drawing the mobile signal --> <dimen name="stat_sys_mobile_signal_corner_radius">0.75dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6ff239ebd2ee..dde4dcfb23fe 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -61,11 +61,26 @@ <!-- When the battery is low, this is displayed to the user in a dialog. The title of the low battery alert. [CHAR LIMIT=NONE]--> <string name="battery_low_title">Battery is low</string> + <!-- When the battery is low and hybrid notifications are enabled, this is displayed to the user in a dialog. + The title of the low battery alert. [CHAR LIMIT=NONE]--> + <string name="battery_low_title_hybrid">Battery is low. Turn on Battery Saver</string> + <!-- A message that appears when the battery level is getting low in a dialog. This is - appened to the subtitle of the low battery alert. "percentage" is the percentage of battery + appended to the subtitle of the low battery alert. "percentage" is the percentage of battery remaining [CHAR LIMIT=none]--> <string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string> + <!-- A message that appears when the battery remaining estimate is low in a dialog. This is + appended to the subtitle of the low battery alert. "percentage" is the percentage of battery + remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]--> + <string name="battery_low_percent_format_hybrid"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left based on your usage</string> + + <!-- A message that appears when the battery remaining estimate is low in a dialog and insufficient + data was present to say it is customized to the user. This is appended to the subtitle of the + low battery alert. "percentage" is the percentage of battery remaining. "time" is the amount + of time remaining before the phone runs out of battery [CHAR LIMIT=none]--> + <string name="battery_low_percent_format_hybrid_short"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left</string> + <!-- Same as battery_low_percent_format, with a notice about battery saver if on. [CHAR LIMIT=none]--> <string name="battery_low_percent_format_saver_started"><xliff:g id="percentage">%s</xliff:g> remaining. Battery Saver is on.</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 173a90a5baa7..64fa9c61280a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -22,4 +22,8 @@ import com.android.systemui.shared.recents.ISystemUiProxy; oneway interface IOverviewProxy { void onBind(in ISystemUiProxy sysUiProxy); void onMotionEvent(in MotionEvent event); + void onQuickSwitch(); + void onQuickScrubStart(); + void onQuickScrubEnd(); + void onQuickScrubProgress(float progress); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 8135c616d87e..d80a33632f3e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -151,6 +151,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mClickActions.clear(); final int subItemsCount = subItems.size(); + final int blendedColor = getTextColor(); for (int i = 0; i < subItemsCount; i++) { SliceItem item = subItems.get(i); @@ -159,7 +160,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe KeyguardSliceButton button = mRow.findViewWithTag(itemTag); if (button == null) { button = new KeyguardSliceButton(mContext); - button.setTextColor(mTextColor); + button.setTextColor(blendedColor); button.setTag(itemTag); } else { mRow.removeView(button); @@ -258,7 +259,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } private void updateTextColors() { - final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); + final int blendedColor = getTextColor(); mTitle.setTextColor(blendedColor); int childCount = mRow.getChildCount(); for (int i = 0; i < childCount; i++) { @@ -322,6 +323,10 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } } + public int getTextColor() { + return ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); + } + /** * Representation of an item that appears under the clock on main keyguard message. * Shows optional separator. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 4b9a8744900d..2873afbca8e9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -16,7 +16,6 @@ package com.android.keyguard; -import android.app.ActivityManager; import android.app.AlarmManager; import android.content.Context; import android.content.res.Configuration; @@ -40,7 +39,6 @@ import android.widget.TextClock; import android.widget.TextView; import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.ChargingView; import com.google.android.collect.Sets; @@ -60,7 +58,6 @@ public class KeyguardStatusView extends GridLayout { private View mClockSeparator; private TextView mOwnerInfo; private ViewGroup mClockContainer; - private ChargingView mBatteryDoze; private KeyguardSliceView mKeyguardSlice; private Runnable mPendingMarqueeStart; private Handler mHandler; @@ -155,11 +152,9 @@ public class KeyguardStatusView extends GridLayout { mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext)); } mOwnerInfo = findViewById(R.id.owner_info); - mBatteryDoze = findViewById(R.id.battery_doze); mKeyguardSlice = findViewById(R.id.keyguard_status_area); mClockSeparator = findViewById(R.id.clock_separator); - mVisibleInDoze = Sets.newArraySet(mBatteryDoze, mClockView, mKeyguardSlice, - mClockSeparator); + mVisibleInDoze = Sets.newArraySet(mClockView, mKeyguardSlice, mClockSeparator); mTextColor = mClockView.getCurrentTextColor(); mKeyguardSlice.setListener(this::onSliceContentChanged); @@ -184,10 +179,6 @@ public class KeyguardStatusView extends GridLayout { mClockView.setTranslationY(translation); mClockView.setScaleX(clockScale); mClockView.setScaleY(clockScale); - final float batteryTranslation = - -(mClockView.getWidth() - (mClockView.getWidth() * clockScale)) / 2; - mBatteryDoze.setTranslationX(batteryTranslation); - mBatteryDoze.setTranslationY(translation); mClockSeparator.setVisibility(hasHeader ? VISIBLE : GONE); } @@ -310,7 +301,7 @@ public class KeyguardStatusView extends GridLayout { } } - public void setDark(float darkAmount) { + public void setDarkAmount(float darkAmount) { if (mDarkAmount == darkAmount) { return; } @@ -331,7 +322,6 @@ public class KeyguardStatusView extends GridLayout { final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount); updateDozeVisibleViews(); - mBatteryDoze.setDark(dark); mKeyguardSlice.setDark(darkAmount); mClockView.setTextColor(blendedTextColor); mClockSeparator.setBackgroundColor(blendedTextColor); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index ab2bce8cc094..e58ad0589c8a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1613,11 +1613,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - private static boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) { + private boolean isBatteryUpdateInteresting(BatteryStatus old, BatteryStatus current) { final boolean nowPluggedIn = current.isPluggedIn(); final boolean wasPluggedIn = old.isPluggedIn(); - final boolean stateChangedWhilePluggedIn = - wasPluggedIn == true && nowPluggedIn == true + final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn && (old.status != current.status); // change in plug state is always interesting @@ -1630,6 +1629,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { return true; } + // change in battery level while keyguard visible + if (mKeyguardIsVisible && old.level != current.level) { + return true; + } + // change where battery needs charging if (!nowPluggedIn && current.isBatteryLow() && current.level != old.level) { return true; diff --git a/packages/SystemUI/src/com/android/systemui/ChargingView.java b/packages/SystemUI/src/com/android/systemui/ChargingView.java deleted file mode 100644 index 33f8b069b751..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ChargingView.java +++ /dev/null @@ -1,126 +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.systemui; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.UserHandle; -import android.util.AttributeSet; -import android.widget.ImageView; - -import com.android.internal.hardware.AmbientDisplayConfiguration; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; - -/** - * A view that only shows its drawable while the phone is charging. - * - * Also reloads its drawable upon density changes. - */ -public class ChargingView extends ImageView implements - BatteryController.BatteryStateChangeCallback, - ConfigurationController.ConfigurationListener { - - private static final long CHARGING_INDICATION_DELAY_MS = 1000; - - private final AmbientDisplayConfiguration mConfig; - private final Runnable mClearSuppressCharging = this::clearSuppressCharging; - private BatteryController mBatteryController; - private int mImageResource; - private boolean mCharging; - private boolean mDark; - private boolean mSuppressCharging; - - - private void clearSuppressCharging() { - mSuppressCharging = false; - removeCallbacks(mClearSuppressCharging); - updateVisibility(); - } - - public ChargingView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - - mConfig = new AmbientDisplayConfiguration(context); - - TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.src}); - int srcResId = a.getResourceId(0, 0); - - if (srcResId != 0) { - mImageResource = srcResId; - } - - a.recycle(); - - updateVisibility(); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - mBatteryController = Dependency.get(BatteryController.class); - mBatteryController.addCallback(this); - Dependency.get(ConfigurationController.class).addCallback(this); - } - - @Override - public void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mBatteryController.removeCallback(this); - Dependency.get(ConfigurationController.class).removeCallback(this); - removeCallbacks(mClearSuppressCharging); - } - - @Override - public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { - boolean startCharging = charging && !mCharging; - if (startCharging && deviceWillWakeUpWhenPluggedIn() && mDark) { - // We're about to wake up, and thus don't want to show the indicator just for it to be - // hidden again. - clearSuppressCharging(); - mSuppressCharging = true; - postDelayed(mClearSuppressCharging, CHARGING_INDICATION_DELAY_MS); - } - mCharging = charging; - updateVisibility(); - } - - private boolean deviceWillWakeUpWhenPluggedIn() { - boolean plugTurnsOnScreen = getResources().getBoolean( - com.android.internal.R.bool.config_unplugTurnsOnScreen); - boolean aod = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT); - return !aod && plugTurnsOnScreen; - } - - @Override - public void onDensityOrFontScaleChanged() { - setImageResource(mImageResource); - } - - public void setDark(boolean dark) { - mDark = dark; - if (!dark) { - clearSuppressCharging(); - } - updateVisibility(); - } - - private void updateVisibility() { - setVisibility(mCharging && !mSuppressCharging && mDark ? VISIBLE : INVISIBLE); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index e7e70afa20ce..7403ddc441f6 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -40,6 +40,8 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.PluginManagerImpl; import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.power.EnhancedEstimates; +import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.power.PowerNotificationWarnings; import com.android.systemui.power.PowerUI; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; @@ -310,6 +312,8 @@ public class Dependency extends SystemUI { mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext)); + mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl()); + // Put all dependencies above here so the factory can override them if it wants. SystemUIFactory.getInstance().injectDependencies(mProviders, mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java index f41425a1f848..5d2e4d09ff43 100644 --- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java +++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java @@ -16,20 +16,14 @@ package com.android.systemui; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + import android.content.Context; -import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.PorterDuff; -import android.graphics.Region; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; import android.view.DisplayCutout; import android.view.Gravity; import android.view.View; @@ -41,9 +35,6 @@ import android.view.WindowManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import java.util.Collections; -import java.util.List; - /** * Emulates a display cutout by drawing its shape in an overlay as supplied by * {@link DisplayCutout}. @@ -101,7 +92,7 @@ public class EmulatedDisplayCutout extends SystemUI implements ConfigurationList PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; - lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; lp.setTitle("EmulatedDisplayCutout"); lp.gravity = Gravity.TOP; return lp; diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java index 6f7a270799bc..c960fa122d50 100644 --- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java +++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java @@ -14,6 +14,8 @@ package com.android.systemui; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + import static com.android.systemui.tuner.TunablePadding.FLAG_START; import static com.android.systemui.tuner.TunablePadding.FLAG_END; @@ -163,7 +165,7 @@ public class RoundedCorners extends SystemUI implements Tunable { | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; lp.setTitle("RoundedOverlay"); lp.gravity = Gravity.TOP; - lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; return lp; } 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 bfe07a980ce7..0486a9dcca74 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -373,7 +373,7 @@ public class PipMenuActivity extends Activity { if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim); } else { - mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim); + mMenuContainerAnimator.playTogether(dismissAnim); } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(MENU_FADE_DURATION); diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java new file mode 100644 index 000000000000..8f41a6036072 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java @@ -0,0 +1,8 @@ +package com.android.systemui.power; + +public interface EnhancedEstimates { + + boolean isHybridNotificationEnabled(); + + Estimate getEstimate(); +} diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java new file mode 100644 index 000000000000..d447542588c5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java @@ -0,0 +1,16 @@ +package com.android.systemui.power; + +import android.util.Log; + +public class EnhancedEstimatesImpl implements EnhancedEstimates { + + @Override + public boolean isHybridNotificationEnabled() { + return false; + } + + @Override + public Estimate getEstimate() { + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java new file mode 100644 index 000000000000..12a8f0a435b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.java @@ -0,0 +1,11 @@ +package com.android.systemui.power; + +public class Estimate { + public final long estimateMillis; + public final boolean isBasedOnUsage; + + public Estimate(long estimateMillis, boolean isBasedOnUsage) { + this.estimateMillis = estimateMillis; + this.isBasedOnUsage = isBasedOnUsage; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c29b362bda13..736286f21bfe 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -17,40 +17,40 @@ package com.android.systemui.power; import android.app.Notification; -import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; +import android.icu.text.MeasureFormat; +import android.icu.text.MeasureFormat.FormatWidth; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import android.media.AudioAttributes; -import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; -import android.os.SystemClock; import android.os.UserHandle; -import android.provider.Settings; import android.support.annotation.VisibleForTesting; +import android.text.format.DateUtils; import android.util.Slog; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.notification.SystemNotificationChannels; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.SystemUI; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.util.NotificationChannels; import java.io.PrintWriter; import java.text.NumberFormat; +import java.util.Locale; +import java.util.concurrent.TimeUnit; public class PowerNotificationWarnings implements PowerUI.WarningsUI { private static final String TAG = PowerUI.TAG + ".Notification"; @@ -96,8 +96,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private long mScreenOffTime; private int mShowing; - private long mBucketDroppedNegativeTimeMs; + private long mWarningTriggerTimeMs; + private Estimate mEstimate; private boolean mWarning; private boolean mPlaySound; private boolean mInvalidCharger; @@ -130,14 +131,22 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { public void update(int batteryLevel, int bucket, long screenOffTime) { mBatteryLevel = batteryLevel; if (bucket >= 0) { - mBucketDroppedNegativeTimeMs = 0; + mWarningTriggerTimeMs = 0; } else if (bucket < mBucket) { - mBucketDroppedNegativeTimeMs = System.currentTimeMillis(); + mWarningTriggerTimeMs = System.currentTimeMillis(); } mBucket = bucket; mScreenOffTime = screenOffTime; } + @Override + public void updateEstimate(Estimate estimate) { + mEstimate = estimate; + if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) { + mWarningTriggerTimeMs = System.currentTimeMillis(); + } + } + private void updateNotification() { if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound=" + mPlaySound + " mInvalidCharger=" + mInvalidCharger); @@ -171,25 +180,43 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL); } - private void showWarningNotification() { - final int textRes = R.string.battery_low_percent_format; + protected void showWarningNotification() { final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0); + // get standard notification copy + String title = mContext.getString(R.string.battery_low_title); + String contentText = mContext.getString(R.string.battery_low_percent_format, percentage); + + // override notification copy if hybrid notification enabled + if (mEstimate != null) { + title = mContext.getString(R.string.battery_low_title_hybrid); + contentText = mContext.getString( + mEstimate.isBasedOnUsage + ? R.string.battery_low_percent_format_hybrid + : R.string.battery_low_percent_format_hybrid_short, + percentage, + getTimeRemainingFormatted()); + } + final Notification.Builder nb = new Notification.Builder(mContext, NotificationChannels.BATTERY) .setSmallIcon(R.drawable.ic_power_low) // Bump the notification when the bucket dropped. - .setWhen(mBucketDroppedNegativeTimeMs) + .setWhen(mWarningTriggerTimeMs) .setShowWhen(false) - .setContentTitle(mContext.getString(R.string.battery_low_title)) - .setContentText(mContext.getString(textRes, percentage)) + .setContentTitle(title) + .setContentText(contentText) .setOnlyAlertOnce(true) .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); + .setVisibility(Notification.VISIBILITY_PUBLIC); if (hasBatterySettings()) { nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS)); } + // Make the notification red if the percentage goes below a certain amount or the time + // remaining estimate is disabled + if (mEstimate == null || mBucket < 0) { + nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError)); + } nb.addAction(0, mContext.getString(R.string.battery_saver_start_action), pendingBroadcast(ACTION_START_SAVER)); @@ -201,6 +228,23 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL); } + @VisibleForTesting + String getTimeRemainingFormatted() { + final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0); + MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW); + + final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS; + final long hours = TimeUnit.MILLISECONDS.toHours( + mEstimate.estimateMillis - remainder); + // round down to the nearest 15 min for now to not appear overly precise + final long minutes = TimeUnit.MILLISECONDS.toMinutes( + remainder - (remainder % TimeUnit.MINUTES.toMillis(15))); + final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR); + final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE); + + return frmt.formatMeasures(hoursMeasure, minutesMeasure); + } + private PendingIntent pendingBroadcast(String action) { return PendingIntent.getBroadcastAsUser(mContext, 0, new Intent(action), 0, UserHandle.CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index a351c09f68b7..c5aab601283c 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; +import java.util.concurrent.TimeUnit; public class PowerUI extends SystemUI { static final String TAG = "PowerUI"; @@ -59,6 +60,7 @@ public class PowerUI extends SystemUI { private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS; private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS; private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer + static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3; private final Handler mHandler = new Handler(); private final Receiver mReceiver = new Receiver(); @@ -68,9 +70,11 @@ public class PowerUI extends SystemUI { private WarningsUI mWarnings; private final Configuration mLastConfiguration = new Configuration(); private int mBatteryLevel = 100; + private long mTimeRemaining = Long.MAX_VALUE; private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN; private int mPlugType = 0; private int mInvalidCharger = 0; + private EnhancedEstimates mEnhancedEstimates; private int mLowBatteryAlertCloseLevel; private final int[] mLowBatteryReminderLevels = new int[2]; @@ -83,8 +87,8 @@ public class PowerUI extends SystemUI { private long mNextLogTime; private IThermalService mThermalService; - // We create a method reference here so that we are guaranteed that we can remove a callback // by using the same instance (method references are not guaranteed to be the same object + // We create a method reference here so that we are guaranteed that we can remove a callback // each time they are created). private final Runnable mUpdateTempCallback = this::updateTemperatureWarning; @@ -94,6 +98,7 @@ public class PowerUI extends SystemUI { mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE); mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime(); mWarnings = Dependency.get(WarningsUI.class); + mEnhancedEstimates = Dependency.get(EnhancedEstimates.class); mLastConfiguration.setTo(mContext.getResources().getConfiguration()); ContentObserver obs = new ContentObserver(mHandler) { @@ -236,21 +241,9 @@ public class PowerUI extends SystemUI { return; } - boolean isPowerSaver = mPowerManager.isPowerSaveMode(); - if (!plugged - && !isPowerSaver - && (bucket < oldBucket || oldPlugged) - && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && bucket < 0) { - - // only play SFX when the dialog comes up or the bucket changes - final boolean playSound = bucket != oldBucket || oldPlugged; - mWarnings.showLowBatteryWarning(playSound); - } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) { - mWarnings.dismissLowBatteryWarning(); - } else { - mWarnings.updateLowBatteryWarning(); - } + // Show the correct version of low battery warning if needed + maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket); + } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOffTime = SystemClock.elapsedRealtime(); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { @@ -261,7 +254,65 @@ public class PowerUI extends SystemUI { Slog.w(TAG, "unknown intent: " + intent); } } - }; + } + + protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, + int bucket) { + boolean isPowerSaver = mPowerManager.isPowerSaveMode(); + // only play SFX when the dialog comes up or the bucket changes + final boolean playSound = bucket != oldBucket || oldPlugged; + long oldTimeRemaining = mTimeRemaining; + if (mEnhancedEstimates.isHybridNotificationEnabled()) { + final Estimate estimate = mEnhancedEstimates.getEstimate(); + // Turbo is not always booted once SysUI is running so we have ot make sure we actually + // get data back + if (estimate != null) { + mTimeRemaining = estimate.estimateMillis; + mWarnings.updateEstimate(estimate); + } + } + + if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, oldTimeRemaining, + mTimeRemaining, + isPowerSaver, mBatteryStatus)) { + mWarnings.showLowBatteryWarning(playSound); + } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining, + isPowerSaver)) { + mWarnings.dismissLowBatteryWarning(); + } else { + mWarnings.updateLowBatteryWarning(); + } + } + + @VisibleForTesting + boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, + int bucket, long oldTimeRemaining, long timeRemaining, + boolean isPowerSaver, int mBatteryStatus) { + return !plugged + && !isPowerSaver + && (((bucket < oldBucket || oldPlugged) && bucket < 0) + || (mEnhancedEstimates.isHybridNotificationEnabled() + && timeRemaining < THREE_HOURS_IN_MILLIS + && isHourLess(oldTimeRemaining, timeRemaining))) + && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN; + } + + private boolean isHourLess(long oldTimeRemaining, long timeRemaining) { + final long dif = oldTimeRemaining - timeRemaining; + return dif >= TimeUnit.HOURS.toMillis(1); + } + + @VisibleForTesting + boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket, + long timeRemaining, boolean isPowerSaver) { + final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled() + && timeRemaining > THREE_HOURS_IN_MILLIS; + final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0); + return isPowerSaver + || plugged + || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled() + || hybridWouldDismiss)); + } private void initTemperatureWarning() { ContentResolver resolver = mContext.getContentResolver(); @@ -433,6 +484,7 @@ public class PowerUI extends SystemUI { public interface WarningsUI { void update(int batteryLevel, int bucket, long screenOffTime); + void updateEstimate(Estimate estimate); void dismissLowBatteryWarning(); void showLowBatteryWarning(boolean playSound); void dismissInvalidChargerWarning(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 33b5268e03e1..7f0acc254a7c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -17,13 +17,18 @@ package com.android.systemui.qs; import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.Point; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.widget.FrameLayout; +import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.qs.customize.QSCustomizer; +import com.android.systemui.statusbar.ExpandableOutlineView; /** * Wrapper view with background which contains {@link QSPanel} and {@link BaseStatusBarHeader} @@ -31,6 +36,7 @@ import com.android.systemui.qs.customize.QSCustomizer; public class QSContainerImpl extends FrameLayout { private final Point mSizePoint = new Point(); + private final Path mClipPath = new Path(); private int mHeightOverride = -1; protected View mQSPanel; @@ -40,6 +46,7 @@ public class QSContainerImpl extends FrameLayout { private QSCustomizer mQSCustomizer; private View mQSFooter; private float mFullElevation; + private float mRadius; public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); @@ -54,6 +61,8 @@ public class QSContainerImpl extends FrameLayout { mQSCustomizer = findViewById(R.id.qs_customize); mQSFooter = findViewById(R.id.qs_footer); mFullElevation = mQSPanel.getElevation(); + mRadius = getResources().getDimensionPixelSize( + Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); setClickable(true); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); @@ -93,6 +102,18 @@ public class QSContainerImpl extends FrameLayout { updateExpansion(); } + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean ret; + canvas.save(); + if (child != mQSCustomizer) { + canvas.clipPath(mClipPath); + } + ret = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + return ret; + } + /** * Overrides the height of this view (post-layout), so that the content is clipped to that * height and the background is set to that height. @@ -110,6 +131,10 @@ public class QSContainerImpl extends FrameLayout { mQSDetail.setBottom(getTop() + height); // Pin QS Footer to the bottom of the panel. mQSFooter.setTranslationY(height - mQSFooter.getHeight()); + + ExpandableOutlineView.getRoundedRectPath(0, 0, getWidth(), height, mRadius, + mRadius, + mClipPath); } protected int calculateContainerHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index c249e3778c0a..0f83078e0738 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -103,7 +103,7 @@ public class QSIconViewImpl extends QSIconView { if (iv instanceof SlashImageView) { ((SlashImageView) iv).setAnimationEnabled(shouldAnimate); - ((SlashImageView) iv).setState(state.slash, d); + ((SlashImageView) iv).setState(null, d); } else { iv.setImageDrawable(d); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index 4d0e60d505f6..acd327b8eae2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -13,7 +13,12 @@ */ package com.android.systemui.qs.tileimpl; +import static com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; @@ -22,16 +27,21 @@ import android.os.Looper; import android.os.Message; import android.service.quicksettings.Tile; import android.text.TextUtils; +import android.util.Log; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; import android.widget.Switch; +import com.android.settingslib.Utils; import com.android.systemui.R; -import com.android.systemui.plugins.qs.*; +import com.android.systemui.plugins.qs.QSIconView; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.BooleanState; public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { @@ -47,6 +57,12 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { private boolean mCollapsedView; private boolean mClicked; + private final ImageView mBg; + private final int mColorActive; + private final int mColorInactive; + private final int mColorDisabled; + private int mCircleColor; + public QSTileBaseView(Context context, QSIconView icon) { this(context, icon, false); } @@ -60,6 +76,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { mIconFrame.setForegroundGravity(Gravity.CENTER); int size = context.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size); addView(mIconFrame, new LayoutParams(size, size)); + mBg = new ImageView(getContext()); + mBg.setScaleType(ScaleType.FIT_CENTER); + mBg.setImageResource(R.drawable.ic_qs_circle); + mIconFrame.addView(mBg); mIcon = icon; FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -73,6 +93,11 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); setBackground(mTileBackground); + mColorActive = Utils.getColorAttr(context, android.R.attr.colorAccent); + mColorDisabled = Utils.getDisabled(context, + Utils.getColorAttr(context, android.R.attr.textColorTertiary)); + mColorInactive = Utils.getColorAttr(context, android.R.attr.textColorSecondary); + setPadding(0, 0, 0, 0); setClipChildren(false); setClipToPadding(false); @@ -80,6 +105,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { setFocusable(true); } + public View getBgCicle() { + return mBg; + } + protected Drawable newTileBackground() { final int[] attrs = new int[]{android.R.attr.selectableItemBackgroundBorderless}; final TypedArray ta = getContext().obtainStyledAttributes(attrs); @@ -150,6 +179,20 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } protected void handleStateChanged(QSTile.State state) { + int circleColor = getCircleColor(state.state); + if (circleColor != mCircleColor) { + if (mBg.isShown()) { + ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor) + .setDuration(QS_ANIM_LENGTH); + animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf( + (Integer) animation.getAnimatedValue()))); + animator.start(); + } else { + QSIconViewImpl.setTint(mBg, circleColor); + } + mCircleColor = circleColor; + } + setClickable(state.state != Tile.STATE_UNAVAILABLE); mIcon.setIcon(state); setContentDescription(state.contentDescription); @@ -163,6 +206,19 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } } + private int getCircleColor(int state) { + switch (state) { + case Tile.STATE_ACTIVE: + return mColorActive; + case Tile.STATE_INACTIVE: + case Tile.STATE_UNAVAILABLE: + return mColorDisabled; + default: + Log.e(TAG, "Invalid state " + state); + return 0; + } + } + @Override public void setClickable(boolean clickable) { super.setClickable(clickable); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 576a447447b5..7259282935a0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -373,11 +373,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile { switch (state) { case Tile.STATE_UNAVAILABLE: return Utils.getDisabled(context, - Utils.getColorAttr(context, android.R.attr.colorForeground)); + Utils.getColorAttr(context, android.R.attr.textColorSecondary)); case Tile.STATE_INACTIVE: - return Utils.getColorAttr(context, android.R.attr.textColorHint); + return Utils.getColorAttr(context, android.R.attr.textColorSecondary); case Tile.STATE_ACTIVE: - return Utils.getColorAttr(context, android.R.attr.textColorPrimary); + return Utils.getColorAttr(context, android.R.attr.colorPrimary); default: Log.e("QSTile", "Invalid state " + state); return 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 85400a15dfc5..43047ed6a5c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -52,6 +52,8 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; +import java.text.NumberFormat; + /** * Controls the indications and error messages shown on the Keyguard */ @@ -87,6 +89,7 @@ public class KeyguardIndicationController { private boolean mPowerCharged; private int mChargingSpeed; private int mChargingWattage; + private int mBatteryLevel; private String mMessageToShowOnScreenOn; private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; @@ -285,14 +288,18 @@ public class KeyguardIndicationController { // Walk down a precedence-ordered list of what indication // should be shown based on user or device state if (mDozing) { - // If we're dozing, never show a persistent indication. + mTextView.setTextColor(Color.WHITE); if (!TextUtils.isEmpty(mTransientIndication)) { // When dozing we ignore any text color and use white instead, because // colors can be hard to read in low brightness. - mTextView.setTextColor(Color.WHITE); mTextView.switchIndication(mTransientIndication); + } else if (mPowerPluggedIn) { + String indication = computePowerIndication(); + mTextView.switchIndication(indication); } else { - mTextView.switchIndication(null); + String percentage = NumberFormat.getPercentInstance() + .format(mBatteryLevel / 100f); + mTextView.switchIndication(percentage); } return; } @@ -422,6 +429,7 @@ public class KeyguardIndicationController { mPowerCharged = status.isCharged(); mChargingWattage = status.maxChargingWattage; mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold); + mBatteryLevel = status.level; updateIndication(); if (mDozing) { if (!wasPluggedIn && mPowerPluggedIn) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java index 78ee04048644..9b123cbea8d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java @@ -14,7 +14,6 @@ package com.android.systemui.statusbar.phone; -import android.graphics.drawable.Drawable; import android.view.View; import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface; @@ -39,6 +38,7 @@ public class ButtonDispatcher { private Integer mAlpha; private Float mDarkIntensity; private Integer mVisibility = -1; + private Boolean mDelayTouchFeedback; private KeyButtonDrawable mImageDrawable; private View mCurrentView; private boolean mVertical; @@ -71,10 +71,10 @@ public class ButtonDispatcher { if (mImageDrawable != null) { ((ButtonInterface) view).setImageDrawable(mImageDrawable); } - - if (view instanceof ButtonInterface) { - ((ButtonInterface) view).setVertical(mVertical); + if (mDelayTouchFeedback != null) { + ((ButtonInterface) view).setDelayTouchFeedback(mDelayTouchFeedback); } + ((ButtonInterface) view).setVertical(mVertical); } public int getId() { @@ -134,6 +134,14 @@ public class ButtonDispatcher { } } + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay); + } + } + public void setOnClickListener(View.OnClickListener clickListener) { mClickListener = clickListener; final int N = mViews.size(); @@ -166,6 +174,14 @@ public class ButtonDispatcher { } } + public void setClickable(boolean clickable) { + abortCurrentGesture(); + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setClickable(clickable); + } + } + public ArrayList<View> getViews() { return mViews; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 01b3b442f2b6..ca66e987933c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -51,6 +51,7 @@ import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.MathUtils; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; @@ -166,6 +167,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private String mLeftButtonStr; private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger(); private boolean mDozing; + private int mIndicationBottomMargin; + private int mIndicationBottomMarginAmbient; + private float mDarkAmount; + private int mBurnInXOffset; public KeyguardBottomAreaView(Context context) { this(context, null); @@ -235,6 +240,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mEnterpriseDisclosure = findViewById( R.id.keyguard_indication_enterprise_disclosure); mIndicationText = findViewById(R.id.keyguard_indication_text); + mIndicationBottomMargin = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom); + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient); updateCameraVisibility(); mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); mUnlockMethodCache.addListener(this); @@ -303,11 +312,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - int indicationBottomMargin = getResources().getDimensionPixelSize( + mIndicationBottomMargin = getResources().getDimensionPixelSize( R.dimen.keyguard_indication_margin_bottom); + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient); MarginLayoutParams mlp = (MarginLayoutParams) mIndicationArea.getLayoutParams(); - if (mlp.bottomMargin != indicationBottomMargin) { - mlp.bottomMargin = indicationBottomMargin; + if (mlp.bottomMargin != mIndicationBottomMargin) { + mlp.bottomMargin = mIndicationBottomMargin; mIndicationArea.setLayoutParams(mlp); } @@ -543,6 +554,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } } + public void setDarkAmount(float darkAmount) { + if (darkAmount == mDarkAmount) { + return; + } + mDarkAmount = darkAmount; + // Let's randomize the bottom margin every time we wake up to avoid burn-in. + if (darkAmount == 0) { + mIndicationBottomMarginAmbient = getResources().getDimensionPixelSize( + R.dimen.keyguard_indication_margin_bottom_ambient) + + (int) (Math.random() * mIndicationText.getTextSize()); + } + mIndicationArea.setAlpha(MathUtils.lerp(1f, 0.7f, darkAmount)); + mIndicationArea.setTranslationY(MathUtils.lerp(0, + mIndicationBottomMargin - mIndicationBottomMarginAmbient, darkAmount)); + } + private static boolean isSuccessfulLaunch(int result) { return result == ActivityManager.START_SUCCESS || result == ActivityManager.START_DELIVERED_TO_TOP @@ -687,11 +714,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL if (mRightAffordanceView.getVisibility() == View.VISIBLE) { startFinishDozeAnimationElement(mRightAffordanceView, delay); } - mIndicationArea.setAlpha(0f); - mIndicationArea.animate() - .alpha(1f) - .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) - .setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION); } private void startFinishDozeAnimationElement(View element, long delay) { @@ -815,6 +837,22 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } } + public void dozeTimeTick() { + if (mDarkAmount == 1) { + // Move indication every minute to avoid burn-in + final int dozeTranslation = mIndicationBottomMargin - mIndicationBottomMarginAmbient; + mIndicationArea.setTranslationY(dozeTranslation + (float) Math.random() * 5); + } + } + + public void setBurnInXOffset(int burnInXOffset) { + if (mBurnInXOffset == burnInXOffset) { + return; + } + mBurnInXOffset = burnInXOffset; + mIndicationArea.setTranslationX(burnInXOffset); + } + private class DefaultLeftButton implements IntentButton { private IconState mIconState = new IconState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index 34486dbcaf43..264f5749fa73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -250,7 +250,7 @@ public class LockIcon extends KeyguardAffordanceView implements OnUserInfoChange } break; case STATE_FACE_UNLOCK: - iconRes = R.drawable.ic_account_circle; + iconRes = R.drawable.ic_face_unlock; break; case STATE_FINGERPRINT: // If screen is off and device asleep, use the draw on animation so the first frame diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java index 6f636aa6299d..4faa84aca099 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java @@ -19,15 +19,14 @@ package com.android.systemui.statusbar.phone; import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.os.RemoteException; import android.util.Log; -import android.view.GestureDetector; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import android.view.ViewConfiguration; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; @@ -37,6 +36,7 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.stackdivider.Divider; import com.android.systemui.tuner.TunerService; @@ -72,6 +72,7 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture private NavigationBarView mNavigationBarView; private boolean mIsVertical; + private final QuickScrubController mQuickScrubController; private final int mScrollTouchSlop; private final Matrix mTransformGlobalMatrix = new Matrix(); private final Matrix mTransformLocalMatrix = new Matrix(); @@ -89,6 +90,7 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture mContext = context; Resources r = context.getResources(); mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance); + mQuickScrubController = new QuickScrubController(context); Dependency.get(TunerService.class).addTunable(this, KEY_DOCK_WINDOW_GESTURE); } @@ -101,10 +103,12 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture mRecentsComponent = recentsComponent; mDivider = divider; mNavigationBarView = navigationBarView; + mQuickScrubController.setComponents(mNavigationBarView); } public void setBarState(boolean isVertical, boolean isRTL) { mIsVertical = isVertical; + mQuickScrubController.setBarState(isVertical, isRTL); } private boolean proxyMotionEvents(MotionEvent event) { @@ -126,7 +130,6 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); - boolean result = false; switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { mTouchDownX = (int) event.getX(); @@ -137,24 +140,26 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix); break; } - case MotionEvent.ACTION_MOVE: { - int x = (int) event.getX(); - int y = (int) event.getY(); - int xDiff = Math.abs(x - mTouchDownX); - int yDiff = Math.abs(y - mTouchDownY); - boolean exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff - || yDiff > mScrollTouchSlop && yDiff > xDiff; - if (exceededTouchSlop) { - result = true; - } - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - break; } - proxyMotionEvents(event); - return result || (mDockWindowEnabled && interceptDockWindowEvent(event)); + if (!mQuickScrubController.onInterceptTouchEvent(event)) { + proxyMotionEvents(event); + return false; + } + return (mDockWindowEnabled && interceptDockWindowEvent(event)); + } + + public void onDraw(Canvas canvas) { + if (mOverviewEventSender.getProxy() != null) { + mQuickScrubController.onDraw(canvas); + } + } + + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + mQuickScrubController.onLayout(changed, left, top, right, bottom); + } + + public void onDarkIntensityChange(float intensity) { + mQuickScrubController.onDarkIntensityChange(intensity); } private boolean interceptDockWindowEvent(MotionEvent event) { @@ -294,7 +299,7 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture } public boolean onTouchEvent(MotionEvent event) { - boolean result = proxyMotionEvents(event); + boolean result = mQuickScrubController.onTouchEvent(event) || proxyMotionEvents(event); if (mDockWindowEnabled) { result |= handleDockWindowEvent(event); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index bd6421c20980..b11367523c08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -138,6 +138,7 @@ public final class NavigationBarTransitions extends BarTransitions { if (mAutoDim) { applyLightsOut(false, true); } + mView.onDarkIntensityChange(darkIntensity); } private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 006a85b7ef8c..059ce929b290 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -26,6 +26,7 @@ import android.app.ActivityManager; import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; @@ -625,6 +626,24 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateRotatedViews(); } + public void onDarkIntensityChange(float intensity) { + if (mGestureHelper != null) { + mGestureHelper.onDarkIntensityChange(intensity); + } + } + + @Override + protected void onDraw(Canvas canvas) { + mGestureHelper.onDraw(canvas); + super.onDraw(canvas); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mGestureHelper.onLayout(changed, left, top, right, bottom); + } + private void updateRotatedViews() { mRotatedViews[Surface.ROTATION_0] = mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 32675d3b2aac..0cc7f5dfbae1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -478,6 +478,7 @@ public class NotificationPanelView extends PanelView implements } mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); mNotificationStackScroller.setDarkShelfOffsetX(mClockPositionResult.clockX); + mKeyguardBottomArea.setBurnInXOffset(mClockPositionResult.clockX); requestScrollerTopPaddingUpdate(animate); } @@ -2608,7 +2609,8 @@ public class NotificationPanelView extends PanelView implements private void setDarkAmount(float amount) { mDarkAmount = amount; - mKeyguardStatusView.setDark(mDarkAmount); + mKeyguardStatusView.setDarkAmount(mDarkAmount); + mKeyguardBottomArea.setDarkAmount(mDarkAmount); positionClockAndNotifications(); } @@ -2630,8 +2632,9 @@ public class NotificationPanelView extends PanelView implements } } - public void refreshTime() { + public void dozeTimeTick() { mKeyguardStatusView.refreshTime(); + mKeyguardBottomArea.dozeTimeTick(); if (mDarkAmount > 0) { positionClockAndNotifications(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java new file mode 100644 index 000000000000..9f8a7efa04f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.view.Display; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.support.annotation.DimenRes; +import com.android.systemui.Dependency; +import com.android.systemui.OverviewProxyService; +import com.android.systemui.R; +import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; +import com.android.systemui.shared.recents.IOverviewProxy; +import com.android.systemui.shared.recents.utilities.Utilities; + +import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM; + +/** + * Class to detect gestures on the navigation bar and implement quick scrub and switch. + */ +public class QuickScrubController extends GestureDetector.SimpleOnGestureListener implements + GestureHelper { + + private static final String TAG = "QuickScrubController"; + private static final int QUICK_SWITCH_FLING_VELOCITY = 0; + private static final int ANIM_DURATION_MS = 200; + private static final long LONG_PRESS_DELAY_MS = 150; + + /** + * For quick step, set a damping value to allow the button to stick closer its origin position + * when dragging before quick scrub is active. + */ + private static final int SWITCH_STICKINESS = 4; + + private NavigationBarView mNavigationBarView; + private GestureDetector mGestureDetector; + + private boolean mDraggingActive; + private boolean mQuickScrubActive; + private float mDownOffset; + private float mTranslation; + private int mTouchDownX; + private int mTouchDownY; + private boolean mDragPositive; + private boolean mIsVertical; + private boolean mIsRTL; + private float mMaxTrackPaintAlpha; + + private final Handler mHandler = new Handler(); + private final Interpolator mQuickScrubEndInterpolator = new DecelerateInterpolator(); + private final Rect mTrackRect = new Rect(); + private final Rect mHomeButtonRect = new Rect(); + private final Paint mTrackPaint = new Paint(); + private final int mScrollTouchSlop; + private final OverviewProxyService mOverviewEventSender; + private final Display mDisplay; + private final int mTrackThickness; + private final int mTrackPadding; + private final ValueAnimator mTrackAnimator; + private final ValueAnimator mButtonAnimator; + private final AnimatorSet mQuickScrubEndAnimator; + private final Context mContext; + + private final AnimatorUpdateListener mTrackAnimatorListener = valueAnimator -> { + mTrackPaint.setAlpha(Math.round((float) valueAnimator.getAnimatedValue() * 255)); + mNavigationBarView.invalidate(); + }; + + private final AnimatorUpdateListener mButtonTranslationListener = animator -> { + int pos = (int) animator.getAnimatedValue(); + if (!mQuickScrubActive) { + pos = mDragPositive ? Math.min((int) mTranslation, pos) : Math.max((int) mTranslation, pos); + } + final View homeView = mNavigationBarView.getHomeButton().getCurrentView(); + if (mIsVertical) { + homeView.setTranslationY(pos); + } else { + homeView.setTranslationX(pos); + } + }; + + private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mNavigationBarView.getHomeButton().setClickable(true); + mQuickScrubActive = false; + mTranslation = 0; + } + }; + + private Runnable mLongPressRunnable = this::startQuickScrub; + + private final GestureDetector.SimpleOnGestureListener mGestureListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) { + if (mQuickScrubActive) { + return false; + } + float velocityX = mIsRTL ? -velX : velX; + float absVelY = Math.abs(velY); + final boolean isValidFling = velocityX > QUICK_SWITCH_FLING_VELOCITY && + mIsVertical ? (absVelY > velocityX) : (velocityX > absVelY); + if (isValidFling) { + mDraggingActive = false; + mButtonAnimator.setIntValues((int) mTranslation, 0); + mButtonAnimator.start(); + mHandler.removeCallbacks(mLongPressRunnable); + try { + final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); + overviewProxy.onQuickSwitch(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send start of quick switch.", e); + } + } + return true; + } + }; + + public QuickScrubController(Context context) { + mContext = context; + mScrollTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mDisplay = ((WindowManager) context.getSystemService( + Context.WINDOW_SERVICE)).getDefaultDisplay(); + mOverviewEventSender = Dependency.get(OverviewProxyService.class); + mGestureDetector = new GestureDetector(mContext, mGestureListener); + mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness); + mTrackPadding = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_edge_padding); + + mTrackAnimator = ObjectAnimator.ofFloat(); + mTrackAnimator.addUpdateListener(mTrackAnimatorListener); + mButtonAnimator = ObjectAnimator.ofInt(); + mButtonAnimator.addUpdateListener(mButtonTranslationListener); + mQuickScrubEndAnimator = new AnimatorSet(); + mQuickScrubEndAnimator.playTogether(mTrackAnimator, mButtonAnimator); + mQuickScrubEndAnimator.setDuration(ANIM_DURATION_MS); + mQuickScrubEndAnimator.addListener(mQuickScrubEndListener); + mQuickScrubEndAnimator.setInterpolator(mQuickScrubEndInterpolator); + } + + public void setComponents(NavigationBarView navigationBarView) { + mNavigationBarView = navigationBarView; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); + final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); + if (overviewProxy == null) { + homeButton.setDelayTouchFeedback(false); + return false; + } + mGestureDetector.onTouchEvent(event); + int action = event.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + int x = (int) event.getX(); + int y = (int) event.getY(); + if (mHomeButtonRect.contains(x, y)) { + mTouchDownX = x; + mTouchDownY = y; + homeButton.setDelayTouchFeedback(true); + mHandler.postDelayed(mLongPressRunnable, LONG_PRESS_DELAY_MS); + } else { + mTouchDownX = mTouchDownY = -1; + } + break; + } + case MotionEvent.ACTION_MOVE: { + if (mTouchDownX != -1) { + int x = (int) event.getX(); + int y = (int) event.getY(); + int xDiff = Math.abs(x - mTouchDownX); + int yDiff = Math.abs(y - mTouchDownY); + boolean exceededTouchSlop; + int pos, touchDown, offset, trackSize; + if (mIsVertical) { + exceededTouchSlop = yDiff > mScrollTouchSlop && yDiff > xDiff; + pos = y; + touchDown = mTouchDownY; + offset = pos - mTrackRect.top; + trackSize = mTrackRect.height(); + } else { + exceededTouchSlop = xDiff > mScrollTouchSlop && xDiff > yDiff; + pos = x; + touchDown = mTouchDownX; + offset = pos - mTrackRect.left; + trackSize = mTrackRect.width(); + } + if (!mDragPositive) { + offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width(); + } + + // Control the button movement + if (!mDraggingActive && exceededTouchSlop) { + boolean allowDrag = !mDragPositive + ? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown; + if (allowDrag) { + mDownOffset = offset; + homeButton.setClickable(false); + mDraggingActive = true; + } + } + if (mDraggingActive && (mDragPositive && offset >= 0 + || !mDragPositive && offset <= 0)) { + float scrubFraction = + Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1); + mTranslation = !mDragPositive + ? Utilities.clamp(offset - mDownOffset, -trackSize, 0) + : Utilities.clamp(offset - mDownOffset, 0, trackSize); + if (mQuickScrubActive) { + try { + overviewProxy.onQuickScrubProgress(scrubFraction); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send progress of quick scrub.", e); + } + } else { + mTranslation /= SWITCH_STICKINESS; + } + if (mIsVertical) { + homeButton.getCurrentView().setTranslationY(mTranslation); + } else { + homeButton.getCurrentView().setTranslationX(mTranslation); + } + } + } + break; + } + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + endQuickScrub(); + break; + } + return mDraggingActive || mQuickScrubActive; + } + + @Override + public void onDraw(Canvas canvas) { + canvas.drawRect(mTrackRect, mTrackPaint); + } + + @Override + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + final int width = right - left; + final int height = bottom - top; + final int x1, x2, y1, y2; + if (mIsVertical) { + x1 = (width - mTrackThickness) / 2; + x2 = x1 + mTrackThickness; + y1 = mDragPositive ? height / 2 : mTrackPadding; + y2 = y1 + height / 2 - mTrackPadding; + } else { + y1 = (height - mTrackThickness) / 2; + y2 = y1 + mTrackThickness; + x1 = mDragPositive ? width / 2 : mTrackPadding; + x2 = x1 + width / 2 - mTrackPadding; + } + mTrackRect.set(x1, y1, x2, y2); + + // Get the touch rect of the home button location + View homeView = mNavigationBarView.getHomeButton().getCurrentView(); + int[] globalHomePos = homeView.getLocationOnScreen(); + int[] globalNavBarPos = mNavigationBarView.getLocationOnScreen(); + int homeX = globalHomePos[0] - globalNavBarPos[0]; + int homeY = globalHomePos[1] - globalNavBarPos[1]; + mHomeButtonRect.set(homeX, homeY, homeX + homeView.getMeasuredWidth(), + homeY + homeView.getMeasuredHeight()); + } + + @Override + public void onDarkIntensityChange(float intensity) { + if (intensity == 0) { + mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_light)); + } else if (intensity == 1) { + mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_dark)); + } + mMaxTrackPaintAlpha = mTrackPaint.getAlpha() * 1f / 255; + mTrackPaint.setAlpha(0); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + endQuickScrub(); + } + return false; + } + + @Override + public void setBarState(boolean isVertical, boolean isRTL) { + mIsVertical = isVertical; + mIsRTL = isRTL; + try { + int navbarPos = WindowManagerGlobal.getWindowManagerService().getNavBarPosition(); + mDragPositive = navbarPos == NAV_BAR_LEFT || navbarPos == NAV_BAR_BOTTOM; + if (isRTL) { + mDragPositive = !mDragPositive; + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get nav bar position.", e); + } + } + + private void startQuickScrub() { + if (!mQuickScrubActive) { + mQuickScrubActive = true; + mTrackAnimator.setFloatValues(0, mMaxTrackPaintAlpha); + mTrackAnimator.start(); + try { + mOverviewEventSender.getProxy().onQuickScrubStart(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send start of quick scrub.", e); + } + } + } + + private void endQuickScrub() { + mHandler.removeCallbacks(mLongPressRunnable); + if (mDraggingActive || mQuickScrubActive) { + mButtonAnimator.setIntValues((int) mTranslation, 0); + mTrackAnimator.setFloatValues(mTrackPaint.getAlpha() * 1f / 255, 0); + mQuickScrubEndAnimator.start(); + try { + mOverviewEventSender.getProxy().onQuickScrubEnd(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send end of quick scrub.", e); + } + } + mDraggingActive = false; + } + + private int getDimensionPixelSize(Context context, @DimenRes int resId) { + return context.getResources().getDimensionPixelSize(resId); + } +} 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 2da1e4d108b4..af65a86676e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1596,7 +1596,7 @@ public class StatusBar extends SystemUI implements DemoMode, final boolean hasArtwork = artworkDrawable != null; - if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) + if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) && !mDozing && (mState != StatusBarState.SHADE || allowWhenShade) && mFingerprintUnlockController.getMode() != FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING @@ -1657,15 +1657,16 @@ public class StatusBar extends SystemUI implements DemoMode, } } } else { - // need to hide the album art, either because we are unlocked or because - // the metadata isn't there to support it + // need to hide the album art, either because we are unlocked, on AOD + // or because the metadata isn't there to support it if (mBackdrop.getVisibility() != View.GONE) { if (DEBUG_MEDIA) { Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); } + boolean cannotAnimateDoze = mDozing && !ScrimState.AOD.getAnimateChange(); if (mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING - || hideBecauseOccluded) { + || hideBecauseOccluded || cannotAnimateDoze) { // We are unlocking directly - no animation! mBackdrop.setVisibility(View.GONE); @@ -4644,7 +4645,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void dozeTimeTick() { - mNotificationPanel.refreshTime(); + mNotificationPanel.dozeTimeTick(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java index cc7943b85bc1..a2bec982ed58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java @@ -26,9 +26,11 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.view.DisplayListCanvas; import android.view.RenderNodeAnimator; import android.view.View; +import android.view.ViewConfiguration; import android.view.animation.Interpolator; import com.android.systemui.Interpolators; @@ -56,14 +58,17 @@ public class KeyButtonRipple extends Drawable { private float mGlowAlpha = 0f; private float mGlowScale = 1f; private boolean mPressed; + private boolean mVisible; private boolean mDrawingHardwareGlow; private int mMaxWidth; private boolean mLastDark; private boolean mDark; + private boolean mDelayTouchFeedback; private final Interpolator mInterpolator = new LogInterpolator(); private boolean mSupportHardware; private final View mTargetView; + private final Handler mHandler = new Handler(); private final HashSet<Animator> mRunningAnimations = new HashSet<>(); private final ArrayList<Animator> mTmpArray = new ArrayList<>(); @@ -77,6 +82,10 @@ public class KeyButtonRipple extends Drawable { mDark = darkIntensity >= 0.5f; } + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + } + private Paint getRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); @@ -211,7 +220,16 @@ public class KeyButtonRipple extends Drawable { } } + /** + * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch + * is enabled. + */ + public void abortDelayedRipple() { + mHandler.removeCallbacksAndMessages(null); + } + private void cancelAnimations() { + mVisible = false; mTmpArray.addAll(mRunningAnimations); int size = mTmpArray.size(); for (int i = 0; i < size; i++) { @@ -220,11 +238,21 @@ public class KeyButtonRipple extends Drawable { } mTmpArray.clear(); mRunningAnimations.clear(); + mHandler.removeCallbacksAndMessages(null); } private void setPressedSoftware(boolean pressed) { if (pressed) { - enterSoftware(); + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterSoftware(); + } + } else { + enterSoftware(); + } } else { exitSoftware(); } @@ -232,6 +260,7 @@ public class KeyButtonRipple extends Drawable { private void enterSoftware() { cancelAnimations(); + mVisible = true; mGlowAlpha = getMaxGlowAlpha(); ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", 0f, GLOW_MAX_SCALE_FACTOR); @@ -240,6 +269,12 @@ public class KeyButtonRipple extends Drawable { scaleAnimator.addListener(mAnimatorListener); scaleAnimator.start(); mRunningAnimations.add(scaleAnimator); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitSoftware(); + } } private void exitSoftware() { @@ -253,7 +288,16 @@ public class KeyButtonRipple extends Drawable { private void setPressedHardware(boolean pressed) { if (pressed) { - enterHardware(); + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterHardware(); + } + } else { + enterHardware(); + } } else { exitHardware(); } @@ -302,6 +346,7 @@ public class KeyButtonRipple extends Drawable { private void enterHardware() { cancelAnimations(); + mVisible = true; mDrawingHardwareGlow = true; setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(), @@ -343,6 +388,12 @@ public class KeyButtonRipple extends Drawable { mRunningAnimations.add(endAnim); invalidateSelf(); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitHardware(); + } } private void exitHardware() { @@ -366,6 +417,7 @@ public class KeyButtonRipple extends Drawable { public void onAnimationEnd(Animator animation) { mRunningAnimations.remove(animation); if (mRunningAnimations.isEmpty() && !mPressed) { + mVisible = false; mDrawingHardwareGlow = false; invalidateSelf(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 05017718d42b..077c6c38c516 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -284,6 +284,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public void abortCurrentGesture() { setPressed(false); + mRipple.abortDelayedRipple(); mGestureAborted = true; } @@ -301,6 +302,11 @@ public class KeyButtonView extends ImageView implements ButtonInterface { } @Override + public void setDelayTouchFeedback(boolean shouldDelay) { + mRipple.setDelayTouchFeedback(shouldDelay); + } + + @Override public void setVertical(boolean vertical) { //no op } diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index 7f07e0c70e8a..3e37cfe75e0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -38,6 +38,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; import com.android.systemui.util.NotificationChannels; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,6 +47,9 @@ import org.mockito.ArgumentCaptor; @SmallTest @RunWith(AndroidJUnit4.class) public class PowerNotificationWarningsTest extends SysuiTestCase { + + public static final String FORMATTED_45M = "0h 45m"; + public static final String FORMATTED_HOUR = "1h 0m"; private final NotificationManager mMockNotificationManager = mock(NotificationManager.class); private PowerNotificationWarnings mPowerNotificationWarnings; @@ -147,4 +151,22 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(), eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any()); } + + @Test + public void testGetTimeRemainingFormatted_roundsDownTo15() { + mPowerNotificationWarnings.updateEstimate( + new Estimate(TimeUnit.MINUTES.toMillis(57), true)); + String time = mPowerNotificationWarnings.getTimeRemainingFormatted(); + + assertTrue("time:" + time + ", expected: " + FORMATTED_45M, time.equals(FORMATTED_45M)); + } + + @Test + public void testGetTimeRemainingFormatted_keepsMinutesWhenZero() { + mPowerNotificationWarnings.updateEstimate( + new Estimate(TimeUnit.MINUTES.toMillis(65), true)); + String time = mPowerNotificationWarnings.getTimeRemainingFormatted(); + + assertTrue("time:" + time + ", expected: " + FORMATTED_HOUR, time.equals(FORMATTED_HOUR)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index e4734a474b62..fdb7f8d005b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -19,12 +19,15 @@ import static android.os.HardwarePropertiesManager.TEMPERATURE_CURRENT; import static android.os.HardwarePropertiesManager.TEMPERATURE_SHUTDOWN; import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.os.BatteryManager; import android.os.HardwarePropertiesManager; import android.provider.Settings; import android.testing.AndroidTestingRunner; @@ -37,6 +40,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.power.PowerUI.WarningsUI; import com.android.systemui.statusbar.phone.StatusBar; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,14 +50,23 @@ import org.junit.runner.RunWith; @SmallTest public class PowerUITest extends SysuiTestCase { + private static final boolean UNPLUGGED = false; + private static final boolean POWER_SAVER_OFF = false; + private static final int ABOVE_WARNING_BUCKET = 1; + public static final int BELOW_WARNING_BUCKET = -1; + public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2); + public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4); private HardwarePropertiesManager mHardProps; private WarningsUI mMockWarnings; private PowerUI mPowerUI; + private EnhancedEstimates mEnhacedEstimates; @Before public void setup() { mMockWarnings = mDependency.injectMockDependency(WarningsUI.class); + mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class); mHardProps = mock(HardwarePropertiesManager.class); + mContext.putComponent(StatusBar.class, mock(StatusBar.class)); mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps); @@ -128,6 +141,180 @@ public class PowerUITest extends SysuiTestCase { verify(mMockWarnings).showHighTemperatureWarning(); } + @Test + public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would not show the non-hybrid notification but would show the + // hybrid + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would show the non-hybrid notification and the hybrid + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would show the non-hybrid but not the hybrid + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertTrue(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // unplugged device that would show the neither due to battery level being good + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, ABOVE_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // plugged device that would show the neither due to being plugged + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // Unknown battery status device that would show the neither due + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN); + assertFalse(shouldShow); + } + + @Test + public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() { + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + mPowerUI.start(); + + // BatterySaverEnabled device that would show the neither due to battery saver + boolean shouldShow = + mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD, + !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD); + assertFalse(shouldShow); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + // device that gets power saver turned on should dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF); + assertTrue(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // device that gets plugged in should dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(!UNPLUGGED, BELOW_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertTrue(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + // would dismiss hybrid but not non-hybrid should not dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertFalse(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // would dismiss non-hybrid but not hybrid should not dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertFalse(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + // should not dismiss when both would not dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertFalse(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true); + + //should dismiss if both would dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertTrue(shouldDismiss); + } + + @Test + public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() { + mPowerUI.start(); + when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false); + + // would dismiss non-hybrid with hybrid disabled should dismiss + boolean shouldDismiss = + mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET, + ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF); + assertTrue(shouldDismiss); + } + private void setCurrentTemp(float temp) { when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT)) .thenReturn(new float[] { temp }); diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 10a809aaae08..04cee676075d 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5134,6 +5134,15 @@ message MetricsEvent { // OS: P ACTION_SCREENSHOT_POWER_MENU = 1282; + // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access + // CATEGORY: SETTINGS + // OS: P + STORAGE_ACCESS = 1283; + + // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access -> Package + // CATEGORY: SETTINGS + // OS: P + APPLICATIONS_STORAGE_DETAIL = 1284; // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java b/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java new file mode 100644 index 000000000000..158084a4be21 --- /dev/null +++ b/services/backup/java/com/android/server/backup/BackupPolicyEnforcer.java @@ -0,0 +1,25 @@ +package com.android.server.backup; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * A helper class to decouple this service from {@link DevicePolicyManager} in order to improve + * testability. + */ +@VisibleForTesting +public class BackupPolicyEnforcer { + private DevicePolicyManager mDevicePolicyManager; + + public BackupPolicyEnforcer(Context context) { + mDevicePolicyManager = + (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); + } + + public ComponentName getMandatoryBackupTransport() { + return mDevicePolicyManager.getMandatoryBackupTransport(); + } +} diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index 03591a812bdd..f33ec55fbf04 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -38,6 +38,7 @@ import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IBackupAgent; import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; import android.app.backup.BackupManager; import android.app.backup.BackupManagerMonitor; import android.app.backup.FullBackup; @@ -207,6 +208,10 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; + // Time delay for initialization operations that can be delayed so as not to consume too much CPU + // on bring-up and increase time-to-UI. + private static final long INITIALIZATION_DELAY_MILLIS = 3000; + // Timeout interval for deciding that a bind or clear-data has taken too long private static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -282,6 +287,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter private final BackupPasswordManager mBackupPasswordManager; + // Time when we post the transport registration operation + private final long mRegisterTransportsRequestedTime; + @GuardedBy("mPendingRestores") private boolean mIsRestoreInProgress; @GuardedBy("mPendingRestores") @@ -674,6 +682,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter @GuardedBy("mQueueLock") private ArrayList<FullBackupEntry> mFullBackupQueue; + private BackupPolicyEnforcer mBackupPolicyEnforcer; + // Utility: build a new random integer token. The low bits are the ordinal of the // operation for near-time uniqueness, and the upper bits are random for app- // side unpredictability. @@ -735,6 +745,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter // Set up our transport options and initialize the default transport SystemConfig systemConfig = SystemConfig.getInstance(); Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist(); + if (transportWhitelist == null) { + transportWhitelist = Collections.emptySet(); + } String transport = Settings.Secure.getString( @@ -749,8 +762,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter new TransportManager( context, transportWhitelist, - transport, - backupThread.getLooper()); + transport); // If encrypted file systems is enabled or disabled, this call will return the // correct directory. @@ -852,15 +864,19 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } mTransportManager = transportManager; - mTransportManager.setTransportBoundListener(mTransportBoundListener); - mTransportManager.registerAllTransports(); + mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered); + mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime(); + mBackupHandler.postDelayed( + mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS); - // Now that we know about valid backup participants, parse any - // leftover journal files into the pending backup set - mBackupHandler.post(this::parseLeftoverJournals); + // Now that we know about valid backup participants, parse any leftover journal files into + // the pending backup set + mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS); // Power management mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*"); + + mBackupPolicyEnforcer = new BackupPolicyEnforcer(context); } private void initPackageTracking() { @@ -1151,39 +1167,28 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } } - private TransportManager.TransportBoundListener mTransportBoundListener = - new TransportManager.TransportBoundListener() { - @Override - public boolean onTransportBound(IBackupTransport transport) { - // If the init sentinel file exists, we need to be sure to perform the init - // as soon as practical. We also create the state directory at registration - // time to ensure it's present from the outset. - String name = null; - try { - name = transport.name(); - String transportDirName = transport.transportDirName(); - File stateDir = new File(mBaseStateDir, transportDirName); - stateDir.mkdirs(); + private void onTransportRegistered(String transportName, String transportDirName) { + if (DEBUG) { + long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime; + Slog.d(TAG, "Transport " + transportName + " registered " + timeMs + + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS + "ms)"); + } - File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); - if (initSentinel.exists()) { - synchronized (mQueueLock) { - mPendingInits.add(name); + File stateDir = new File(mBaseStateDir, transportDirName); + stateDir.mkdirs(); - // TODO: pick a better starting time than now + 1 minute - long delay = 1000 * 60; // one minute, in milliseconds - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + delay, mRunInitIntent); - } - } - return true; - } catch (Exception e) { - // the transport threw when asked its file naming prefs; declare it invalid - Slog.w(TAG, "Failed to regiser transport: " + name); - return false; - } - } - }; + File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); + if (initSentinel.exists()) { + synchronized (mQueueLock) { + mPendingInits.add(transportName); + + // TODO: pick a better starting time than now + 1 minute + long delay = 1000 * 60; // one minute, in milliseconds + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, mRunInitIntent); + } + } + } // ----- Track installation/removal of packages ----- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -2774,6 +2779,10 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public void setBackupEnabled(boolean enable) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "setBackupEnabled"); + if (!enable && mBackupPolicyEnforcer.getMandatoryBackupTransport() != null) { + Slog.w(TAG, "Cannot disable backups when the mandatory backups policy is active."); + return; + } Slog.i(TAG, "Backup enabled => " + enable); @@ -2891,14 +2900,14 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports"); - return mTransportManager.getBoundTransportNames(); + return mTransportManager.getRegisteredTransportNames(); } @Override public ComponentName[] listAllTransportComponents() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransportComponents"); - return mTransportManager.getAllTransportComponents(); + return mTransportManager.getRegisteredTransportComponents(); } @Override @@ -3003,10 +3012,18 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter /** Selects transport {@code transportName} and returns previous selected transport. */ @Override + @Deprecated + @Nullable public String selectBackupTransport(String transportName) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "selectBackupTransport"); + if (!isAllowedByMandatoryBackupTransportPolicy(transportName)) { + // Don't change the transport if it is not allowed. + Slog.w(TAG, "Failed to select transport - disallowed by device owner policy."); + return mTransportManager.getCurrentTransportName(); + } + final long oldId = Binder.clearCallingIdentity(); try { String previousTransportName = mTransportManager.selectTransport(transportName); @@ -3021,10 +3038,20 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter @Override public void selectBackupTransportAsync( - ComponentName transportComponent, ISelectBackupTransportCallback listener) { + ComponentName transportComponent, @Nullable ISelectBackupTransportCallback listener) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "selectBackupTransportAsync"); - + if (!isAllowedByMandatoryBackupTransportPolicy(transportComponent)) { + try { + if (listener != null) { + Slog.w(TAG, "Failed to select transport - disallowed by device owner policy."); + listener.onFailure(BackupManager.ERROR_BACKUP_NOT_ALLOWED); + } + } catch (RemoteException e) { + Slog.e(TAG, "ISelectBackupTransportCallback listener not available"); + } + return; + } final long oldId = Binder.clearCallingIdentity(); try { String transportString = transportComponent.flattenToShortString(); @@ -3046,10 +3073,12 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } try { - if (transportName != null) { - listener.onSuccess(transportName); - } else { - listener.onFailure(result); + if (listener != null) { + if (transportName != null) { + listener.onSuccess(transportName); + } else { + listener.onFailure(result); + } } } catch (RemoteException e) { Slog.e(TAG, "ISelectBackupTransportCallback listener not available"); @@ -3060,6 +3089,38 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } } + /** + * Returns if the specified transport can be set as the current transport without violating the + * mandatory backup transport policy. + */ + private boolean isAllowedByMandatoryBackupTransportPolicy(String transportName) { + ComponentName mandatoryBackupTransport = mBackupPolicyEnforcer.getMandatoryBackupTransport(); + if (mandatoryBackupTransport == null) { + return true; + } + final String mandatoryBackupTransportName; + try { + mandatoryBackupTransportName = + mTransportManager.getTransportName(mandatoryBackupTransport); + } catch (TransportNotRegisteredException e) { + Slog.e(TAG, "mandatory backup transport not registered!"); + return false; + } + return TextUtils.equals(mandatoryBackupTransportName, transportName); + } + + /** + * Returns if the specified transport can be set as the current transport without violating the + * mandatory backup transport policy. + */ + private boolean isAllowedByMandatoryBackupTransportPolicy(ComponentName transport) { + ComponentName mandatoryBackupTransport = mBackupPolicyEnforcer.getMandatoryBackupTransport(); + if (mandatoryBackupTransport == null) { + return true; + } + return mandatoryBackupTransport.equals(transport); + } + private void updateStateForTransport(String newTransportName) { // Publish the name change Settings.Secure.putString(mContext.getContentResolver(), diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index 34b8935939ff..09456b68e1c0 100644 --- a/services/backup/java/com/android/server/backup/TransportManager.java +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -16,93 +16,56 @@ package com.android.server.backup; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; - import android.annotation.Nullable; +import android.annotation.WorkerThread; import android.app.backup.BackupManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; +import com.android.server.backup.transport.OnTransportRegisteredListener; import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportConnectionListener; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; -/** - * Handles in-memory bookkeeping of all BackupTransport objects. - */ +/** Handles in-memory bookkeeping of all BackupTransport objects. */ public class TransportManager { - private static final String TAG = "BackupTransportManager"; @VisibleForTesting public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; - private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec - private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins - private static final int REBINDING_TIMEOUT_MSG = 1; - private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); private final Context mContext; private final PackageManager mPackageManager; private final Set<ComponentName> mTransportWhitelist; - private final Handler mHandler; private final TransportClientManager mTransportClientManager; - - /** - * This listener is called after we bind to any transport. If it returns true, this is a valid - * transport. - */ - private TransportBoundListener mTransportBoundListener; - private final Object mTransportLock = new Object(); - - /** - * We have detected these transports on the device. Unless in exceptional cases, we are also - * bound to all of these. - */ - @GuardedBy("mTransportLock") - private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>(); - - /** We are currently bound to these transports. */ - @GuardedBy("mTransportLock") - private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>(); - - /** @see #getEligibleTransportComponents() */ - @GuardedBy("mTransportLock") - private final Set<ComponentName> mEligibleTransports = new ArraySet<>(); + private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {}; /** @see #getRegisteredTransportNames() */ @GuardedBy("mTransportLock") @@ -110,120 +73,98 @@ public class TransportManager { new ArrayMap<>(); @GuardedBy("mTransportLock") + @Nullable private volatile String mCurrentTransportName; - TransportManager( - Context context, - Set<ComponentName> whitelist, - String defaultTransport, - TransportBoundListener listener, - Looper looper) { - this(context, whitelist, defaultTransport, looper); - mTransportBoundListener = listener; + TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) { + this(context, whitelist, selectedTransport, new TransportClientManager(context)); } + @VisibleForTesting TransportManager( Context context, Set<ComponentName> whitelist, - String defaultTransport, - Looper looper) { + String selectedTransport, + TransportClientManager transportClientManager) { mContext = context; mPackageManager = context.getPackageManager(); - if (whitelist != null) { - mTransportWhitelist = whitelist; - } else { - mTransportWhitelist = new ArraySet<>(); - } - mCurrentTransportName = defaultTransport; - mHandler = new RebindOnTimeoutHandler(looper); - mTransportClientManager = new TransportClientManager(context); + mTransportWhitelist = Preconditions.checkNotNull(whitelist); + mCurrentTransportName = selectedTransport; + mTransportClientManager = transportClientManager; } - public void setTransportBoundListener(TransportBoundListener transportBoundListener) { - mTransportBoundListener = transportBoundListener; + /* Sets a listener to be called whenever a transport is registered. */ + public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) { + mOnTransportRegisteredListener = listener; } + @WorkerThread void onPackageAdded(String packageName) { - // New package added. Bind to all transports it contains. - synchronized (mTransportLock) { - log_verbose("Package added. Binding to all transports. " + packageName); - bindToAllInternal(packageName, null /* all components */); - } + registerTransportsFromPackage(packageName, transportComponent -> true); } void onPackageRemoved(String packageName) { - // Package removed. Remove all its transports from our list. These transports have already - // been removed from mBoundTransports because onServiceDisconnected would already been - // called on TransportConnection objects. synchronized (mTransportLock) { - Iterator<Map.Entry<ComponentName, TransportConnection>> iter = - mValidTransports.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry<ComponentName, TransportConnection> validTransport = iter.next(); - ComponentName componentName = validTransport.getKey(); - if (componentName.getPackageName().equals(packageName)) { - TransportConnection transportConnection = validTransport.getValue(); - iter.remove(); - if (transportConnection != null) { - mContext.unbindService(transportConnection); - log_verbose("Package removed, removing transport: " - + componentName.flattenToShortString()); - } - } - } - removeTransportsIfLocked( - componentName -> packageName.equals(componentName.getPackageName())); + mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName)); } } - void onPackageChanged(String packageName, String[] components) { + @WorkerThread + void onPackageChanged(String packageName, String... components) { synchronized (mTransportLock) { - // Remove all changed components from mValidTransports. We'll bind to them again - // and re-add them if still valid. - Set<ComponentName> transportsToBeRemoved = new ArraySet<>(); - for (String component : components) { - ComponentName componentName = new ComponentName(packageName, component); - transportsToBeRemoved.add(componentName); - TransportConnection removed = mValidTransports.remove(componentName); - if (removed != null) { - mContext.unbindService(removed); - log_verbose("Package changed. Removing transport: " + - componentName.flattenToShortString()); - } - } - removeTransportsIfLocked(transportsToBeRemoved::contains); - bindToAllInternal(packageName, components); + Set<ComponentName> transportComponents = + Stream.of(components) + .map(component -> new ComponentName(packageName, component)) + .collect(Collectors.toSet()); + + mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains); + registerTransportsFromPackage(packageName, transportComponents::contains); } } - @GuardedBy("mTransportLock") - private void removeTransportsIfLocked(Predicate<ComponentName> filter) { - mEligibleTransports.removeIf(filter); - mRegisteredTransportsDescriptionMap.keySet().removeIf(filter); + /** + * Returns the {@link ComponentName}s of the registered transports. + * + * <p>A *registered* transport is a transport that satisfies intent with action + * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)} + * and that we have successfully connected to once. + */ + ComponentName[] getRegisteredTransportComponents() { + synchronized (mTransportLock) { + return mRegisteredTransportsDescriptionMap + .keySet() + .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]); + } } - public IBackupTransport getTransportBinder(String transportName) { + /** + * Returns the names of the registered transports. + * + * @see #getRegisteredTransportComponents() + */ + String[] getRegisteredTransportNames() { synchronized (mTransportLock) { - ComponentName component = mBoundTransports.get(transportName); - if (component == null) { - Slog.w(TAG, "Transport " + transportName + " not bound."); - return null; - } - TransportConnection conn = mValidTransports.get(component); - if (conn == null) { - Slog.w(TAG, "Transport " + transportName + " not valid."); - return null; - } - return conn.getBinder(); + return mRegisteredTransportsDescriptionMap + .values() + .stream() + .map(transportDescription -> transportDescription.name) + .toArray(String[]::new); } } - public IBackupTransport getCurrentTransportBinder() { - return getTransportBinder(mCurrentTransportName); + /** Returns a set with the whitelisted transports. */ + Set<ComponentName> getTransportWhitelist() { + return mTransportWhitelist; + } + + @Nullable + String getCurrentTransportName() { + return mCurrentTransportName; } /** * Returns the transport name associated with {@code transportComponent}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ public String getTransportName(ComponentName transportComponent) @@ -234,33 +175,46 @@ public class TransportManager { } /** - * Retrieve the configuration intent of {@code transportName}. + * Retrieves the transport dir name of {@code transportComponent}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ - @Nullable - public Intent getTransportConfigurationIntent(String transportName) + public String getTransportDirName(ComponentName transportComponent) throws TransportNotRegisteredException { synchronized (mTransportLock) { - return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .configurationIntent; + return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) + .transportDirName; + } + } + + /** + * Retrieves the transport dir name of {@code transportName}. + * + * @throws TransportNotRegisteredException if the transport is not registered. + */ + public String getTransportDirName(String transportName) throws TransportNotRegisteredException { + synchronized (mTransportLock) { + return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName; } } /** - * Retrieve the data management intent of {@code transportName}. + * Retrieves the configuration intent of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable - public Intent getTransportDataManagementIntent(String transportName) + public Intent getTransportConfigurationIntent(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .dataManagementIntent; + .configurationIntent; } } /** - * Retrieve the current destination string of {@code transportName}. + * Retrieves the current destination string of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ public String getTransportCurrentDestinationString(String transportName) @@ -272,66 +226,101 @@ public class TransportManager { } /** - * Retrieve the data management label of {@code transportName}. + * Retrieves the data management intent of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable - public String getTransportDataManagementLabel(String transportName) + public Intent getTransportDataManagementIntent(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .dataManagementLabel; + .dataManagementIntent; } } /** - * Retrieve the transport dir name of {@code transportName}. + * Retrieves the data management label of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ - public String getTransportDirName(String transportName) + @Nullable + public String getTransportDataManagementLabel(String transportName) throws TransportNotRegisteredException { synchronized (mTransportLock) { return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .transportDirName; + .dataManagementLabel; } } - /** - * Retrieve the transport dir name of {@code transportComponent}. - * @throws TransportNotRegisteredException if the transport is not registered. - */ - public String getTransportDirName(ComponentName transportComponent) - throws TransportNotRegisteredException { + /* Returns true if the transport identified by {@code transportName} is registered. */ + public boolean isTransportRegistered(String transportName) { synchronized (mTransportLock) { - return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) - .transportDirName; + return getRegisteredTransportEntryLocked(transportName) != null; } } /** * Execute {@code transportConsumer} for each registered transport passing the transport name. * This is called with an internal lock held, ensuring that the transport will remain registered - * while {@code transportConsumer} is being executed. Don't do heavy operations in - * {@code transportConsumer}. + * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code + * transportConsumer}. */ public void forEachRegisteredTransport(Consumer<String> transportConsumer) { synchronized (mTransportLock) { - for (TransportDescription transportDescription - : mRegisteredTransportsDescriptionMap.values()) { + for (TransportDescription transportDescription : + mRegisteredTransportsDescriptionMap.values()) { transportConsumer.accept(transportDescription.name); } } } - public String getTransportName(IBackupTransport binder) { + /** + * Updates given values for the transport already registered and identified with {@param + * transportComponent}. If the transport is not registered it will log and return. + */ + public void updateTransportAttributes( + ComponentName transportComponent, + String name, + @Nullable Intent configurationIntent, + String currentDestinationString, + @Nullable Intent dataManagementIntent, + @Nullable String dataManagementLabel) { synchronized (mTransportLock) { - for (TransportConnection conn : mValidTransports.values()) { - if (conn.getBinder() == binder) { - return conn.getName(); - } + TransportDescription description = + mRegisteredTransportsDescriptionMap.get(transportComponent); + if (description == null) { + Slog.e(TAG, "Transport " + name + " not registered tried to change description"); + return; } + description.name = name; + description.configurationIntent = configurationIntent; + description.currentDestinationString = currentDestinationString; + description.dataManagementIntent = dataManagementIntent; + description.dataManagementLabel = dataManagementLabel; + Slog.d(TAG, "Transport " + name + " updated its attributes"); } - return null; + } + + @GuardedBy("mTransportLock") + private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( + ComponentName transportComponent) throws TransportNotRegisteredException { + TransportDescription description = + mRegisteredTransportsDescriptionMap.get(transportComponent); + if (description == null) { + throw new TransportNotRegisteredException(transportComponent); + } + return description; + } + + @GuardedBy("mTransportLock") + private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( + String transportName) throws TransportNotRegisteredException { + TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); + if (description == null) { + throw new TransportNotRegisteredException(transportName); + } + return description; } @GuardedBy("mTransportLock") @@ -351,21 +340,11 @@ public class TransportManager { } @GuardedBy("mTransportLock") - private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( - String transportName) throws TransportNotRegisteredException { - TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); - if (description == null) { - throw new TransportNotRegisteredException(transportName); - } - return description; - } - - @GuardedBy("mTransportLock") @Nullable private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked( String transportName) { - for (Map.Entry<ComponentName, TransportDescription> entry - : mRegisteredTransportsDescriptionMap.entrySet()) { + for (Map.Entry<ComponentName, TransportDescription> entry : + mRegisteredTransportsDescriptionMap.entrySet()) { TransportDescription description = entry.getValue(); if (transportName.equals(description.name)) { return entry; @@ -374,17 +353,16 @@ public class TransportManager { return null; } - @GuardedBy("mTransportLock") - private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( - ComponentName transportComponent) throws TransportNotRegisteredException { - TransportDescription description = - mRegisteredTransportsDescriptionMap.get(transportComponent); - if (description == null) { - throw new TransportNotRegisteredException(transportComponent); - } - return description; - } - + /** + * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not + * registered. + * + * @param transportName The name of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not registered. + */ @Nullable public TransportClient getTransportClient(String transportName, String caller) { try { @@ -395,6 +373,16 @@ public class TransportManager { } } + /** + * Returns a {@link TransportClient} for {@code transportName} or throws if not registered. + * + * @param transportName The name of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient}. + * @throws TransportNotRegisteredException if the transport is not registered. + */ public TransportClient getTransportClientOrThrow(String transportName, String caller) throws TransportNotRegisteredException { synchronized (mTransportLock) { @@ -406,19 +394,14 @@ public class TransportManager { } } - public boolean isTransportRegistered(String transportName) { - synchronized (mTransportLock) { - return getRegisteredTransportEntryLocked(transportName) != null; - } - } - /** - * Returns a {@link TransportClient} for the current transport or null if not found. + * Returns a {@link TransportClient} for the current transport or {@code null} if not + * registered. * * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient} or null if not found. + * @return A {@link TransportClient} or null if not registered. */ @Nullable public TransportClient getCurrentTransportClient(String caller) { @@ -455,130 +438,88 @@ public class TransportManager { mTransportClientManager.disposeOfTransportClient(transportClient, caller); } - String[] getBoundTransportNames() { - synchronized (mTransportLock) { - return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]); - } - } - - ComponentName[] getAllTransportComponents() { - synchronized (mTransportLock) { - return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]); - } - } - /** - * An *eligible* transport is a service component that satisfies intent with action - * android.backup.TRANSPORT_HOST and returns true for - * {@link #isTransportTrusted(ComponentName)}. It may be registered or not registered. - * This method returns the {@link ComponentName}s of those transports. + * Sets {@code transportName} as selected transport and returns previously selected transport + * name. If there was no previous transport it returns null. + * + * <p>You should NOT call this method in new code. This won't make any checks against {@code + * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or + * another error at the time it's being executed. + * + * <p>{@link Deprecated} as public, this method can be used as private. */ - ComponentName[] getEligibleTransportComponents() { + @Deprecated + @Nullable + String selectTransport(String transportName) { synchronized (mTransportLock) { - return mEligibleTransports.toArray(new ComponentName[mEligibleTransports.size()]); + String prevTransport = mCurrentTransportName; + mCurrentTransportName = transportName; + return prevTransport; } } - Set<ComponentName> getTransportWhitelist() { - return mTransportWhitelist; - } - /** - * A *registered* transport is an eligible transport that has been successfully connected and - * that returned true for method - * {@link TransportBoundListener#onTransportBound(IBackupTransport)} of TransportBoundListener - * provided in the constructor. This method returns the names of the registered transports. + * Tries to register the transport if not registered. If successful also selects the transport. + * + * @param transportComponent Host of the transport. + * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} + * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. */ - String[] getRegisteredTransportNames() { + @WorkerThread + public int registerAndSelectTransport(ComponentName transportComponent) { synchronized (mTransportLock) { - return mRegisteredTransportsDescriptionMap.values().stream() - .map(transportDescription -> transportDescription.name) - .toArray(String[]::new); - } - } + if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) { + int result = registerTransport(transportComponent); + if (result != BackupManager.SUCCESS) { + return result; + } + } - /** - * Updates given values for the transport already registered and identified with - * {@param transportComponent}. If the transport is not registered it will log and return. - */ - public void updateTransportAttributes( - ComponentName transportComponent, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - @Nullable String dataManagementLabel) { - synchronized (mTransportLock) { - TransportDescription description = - mRegisteredTransportsDescriptionMap.get(transportComponent); - if (description == null) { - Slog.e(TAG, "Transport " + name + " not registered tried to change description"); - return; + try { + selectTransport(getTransportName(transportComponent)); + return BackupManager.SUCCESS; + } catch (TransportNotRegisteredException e) { + // Shouldn't happen because we are holding the lock + Slog.wtf(TAG, "Transport unexpectedly not registered"); + return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } - description.name = name; - description.configurationIntent = configurationIntent; - description.currentDestinationString = currentDestinationString; - description.dataManagementIntent = dataManagementIntent; - description.dataManagementLabel = dataManagementLabel; - Slog.d(TAG, "Transport " + name + " updated its attributes"); } } - @Nullable - String getCurrentTransportName() { - return mCurrentTransportName; - } - - // This is for mocking, Mockito can't mock if package-protected and in the same package but - // different class loaders. Checked with the debugger and class loaders are different - // See https://github.com/mockito/mockito/issues/796 - @VisibleForTesting(visibility = PACKAGE) - public void registerAllTransports() { - bindToAllInternal(null /* all packages */, null /* all components */); + @WorkerThread + public void registerTransports() { + registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true); } - /** - * Bind to all transports belonging to the given package and the given component list. - * null acts a wildcard. - * - * If packageName is null, bind to all transports in all packages. - * If components is null, bind to all transports in the given package. - */ - private void bindToAllInternal(String packageName, String[] components) { - PackageInfo pkgInfo = null; - if (packageName != null) { - try { - pkgInfo = mPackageManager.getPackageInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Package not found: " + packageName); - return; - } + @WorkerThread + private void registerTransportsFromPackage( + String packageName, Predicate<ComponentName> transportComponentFilter) { + try { + mPackageManager.getPackageInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Trying to register transports from package not found " + packageName); + return; } - Intent intent = new Intent(mTransportServiceIntent); - if (packageName != null) { - intent.setPackage(packageName); - } + registerTransportsForIntent( + new Intent(mTransportServiceIntent).setPackage(packageName), + transportComponentFilter.and(fromPackageFilter(packageName))); + } - List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( - intent, 0, UserHandle.USER_SYSTEM); - if (hosts != null) { + @WorkerThread + private void registerTransportsForIntent( + Intent intent, Predicate<ComponentName> transportComponentFilter) { + List<ResolveInfo> hosts = + mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM); + if (hosts == null) { + return; + } + synchronized (mTransportLock) { for (ResolveInfo host : hosts) { - final ComponentName infoComponentName = getComponentName(host.serviceInfo); - boolean shouldBind = false; - if (components != null && packageName != null) { - for (String component : components) { - ComponentName cn = new ComponentName(pkgInfo.packageName, component); - if (infoComponentName.equals(cn)) { - shouldBind = true; - break; - } - } - } else { - shouldBind = true; - } - if (shouldBind && isTransportTrusted(infoComponentName)) { - tryBindTransport(infoComponentName); + ComponentName transportComponent = host.serviceInfo.getComponentName(); + if (transportComponentFilter.test(transportComponent) + && isTransportTrusted(transportComponent)) { + registerTransport(transportComponent); } } } @@ -605,64 +546,6 @@ public class TransportManager { return true; } - private void tryBindTransport(ComponentName transportComponentName) { - Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString()); - // TODO: b/22388012 (Multi user backup and restore) - TransportConnection connection = new TransportConnection(transportComponentName); - synchronized (mTransportLock) { - mEligibleTransports.add(transportComponentName); - } - if (bindToTransport(transportComponentName, connection)) { - synchronized (mTransportLock) { - mValidTransports.put(transportComponentName, connection); - } - } else { - Slog.w(TAG, "Couldn't bind to transport " + transportComponentName); - } - } - - private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) { - Intent intent = new Intent(mTransportServiceIntent) - .setComponent(componentName); - return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, - createSystemUserHandle()); - } - - String selectTransport(String transportName) { - synchronized (mTransportLock) { - String prevTransport = mCurrentTransportName; - mCurrentTransportName = transportName; - return prevTransport; - } - } - - /** - * Tries to register the transport if not registered. If successful also selects the transport. - * - * @param transportComponent Host of the transport. - * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} - * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. - */ - public int registerAndSelectTransport(ComponentName transportComponent) { - synchronized (mTransportLock) { - if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) { - int result = registerTransport(transportComponent); - if (result != BackupManager.SUCCESS) { - return result; - } - } - - try { - selectTransport(getTransportName(transportComponent)); - return BackupManager.SUCCESS; - } catch (TransportNotRegisteredException e) { - // Shouldn't happen because we are holding the lock - Slog.wtf(TAG, "Transport unexpectedly not registered"); - return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; - } - } - } - /** * Tries to register transport represented by {@code transportComponent}. * @@ -670,7 +553,12 @@ public class TransportManager { * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. */ + @WorkerThread private int registerTransport(ComponentName transportComponent) { + if (!isTransportTrusted(transportComponent)) { + return BackupManager.ERROR_TRANSPORT_INVALID; + } + String transportString = transportComponent.flattenToShortString(); String callerLogString = "TransportManager.registerTransport()"; @@ -689,26 +577,21 @@ public class TransportManager { EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1); int result; - if (isTransportValid(transport)) { - try { - registerTransport(transportComponent, transport); - // If registerTransport() hasn't thrown... - result = BackupManager.SUCCESS; - } catch (RemoteException e) { - Slog.e(TAG, "Transport " + transportString + " died while registering"); - result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; - } - } else { - Slog.w(TAG, "Can't register invalid transport " + transportString); - result = BackupManager.ERROR_TRANSPORT_INVALID; - } - - mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); - if (result == BackupManager.SUCCESS) { + try { + String transportName = transport.name(); + String transportDirName = transport.transportDirName(); + registerTransport(transportComponent, transport); + // If registerTransport() hasn't thrown... Slog.d(TAG, "Transport " + transportString + " registered"); - } else { + mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName); + result = BackupManager.SUCCESS; + } catch (RemoteException e) { + Slog.e(TAG, "Transport " + transportString + " died while registering"); EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0); + result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } + + mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); return result; } @@ -717,204 +600,20 @@ public class TransportManager { throws RemoteException { synchronized (mTransportLock) { String name = transport.name(); - TransportDescription description = new TransportDescription( - name, - transport.transportDirName(), - transport.configurationIntent(), - transport.currentDestinationString(), - transport.dataManagementIntent(), - transport.dataManagementLabel()); + TransportDescription description = + new TransportDescription( + name, + transport.transportDirName(), + transport.configurationIntent(), + transport.currentDestinationString(), + transport.dataManagementIntent(), + transport.dataManagementLabel()); mRegisteredTransportsDescriptionMap.put(transportComponent, description); } } - private boolean isTransportValid(IBackupTransport transport) { - if (mTransportBoundListener == null) { - Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid"); - return false; - } - return mTransportBoundListener.onTransportBound(transport); - } - - private class TransportConnection implements ServiceConnection { - - // Hold mTransportLock to access these fields so as to provide a consistent view of them. - private volatile IBackupTransport mBinder; - private volatile String mTransportName; - - private final ComponentName mTransportComponent; - - private TransportConnection(ComponentName transportComponent) { - mTransportComponent = transportComponent; - } - - @Override - public void onServiceConnected(ComponentName component, IBinder binder) { - synchronized (mTransportLock) { - mBinder = IBackupTransport.Stub.asInterface(binder); - boolean success = false; - - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - component.flattenToShortString(), 1); - - try { - mTransportName = mBinder.name(); - // BackupManager requests some fields from the transport. If they are - // invalid, throw away this transport. - if (isTransportValid(mBinder)) { - // We're now using the always-bound connection to do the registration but - // when we remove the always-bound code this will be in the first binding - // TODO: Move registration to first binding - registerTransport(component, mBinder); - // If registerTransport() hasn't thrown... - success = true; - } - } catch (RemoteException e) { - success = false; - Slog.e(TAG, "Couldn't get transport name.", e); - } finally { - // we need to intern() the String of the component, so that we can use it with - // Handler's removeMessages(), which uses == operator to compare the tokens - String componentShortString = component.flattenToShortString().intern(); - if (success) { - Slog.d(TAG, "Bound to transport: " + componentShortString); - mBoundTransports.put(mTransportName, component); - // cancel rebinding on timeout for this component as we've already connected - mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString); - } else { - Slog.w(TAG, "Bound to transport " + componentShortString + - " but it is invalid"); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - componentShortString, 0); - mContext.unbindService(this); - mValidTransports.remove(component); - mEligibleTransports.remove(component); - mBinder = null; - } - } - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - synchronized (mTransportLock) { - mBinder = null; - mBoundTransports.remove(mTransportName); - } - String componentShortString = component.flattenToShortString(); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0); - Slog.w(TAG, "Disconnected from transport " + componentShortString); - scheduleRebindTimeout(component); - } - - /** - * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically - * for a few minutes after the binding went away. - */ - private void scheduleRebindTimeout(ComponentName component) { - // we need to intern() the String of the component, so that we can use it with Handler's - // removeMessages(), which uses == operator to compare the tokens - final String componentShortString = component.flattenToShortString().intern(); - final long rebindTimeout = getRebindTimeout(); - mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString); - Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG); - msg.obj = componentShortString; - mHandler.sendMessageDelayed(msg, rebindTimeout); - Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in " - + rebindTimeout + "ms"); - } - - // Intentionally not synchronized -- the variable is volatile and changes to its value - // are inside synchronized blocks, providing a memory sync barrier; and this method - // does not touch any other state protected by that lock. - private IBackupTransport getBinder() { - return mBinder; - } - - // Intentionally not synchronized; same as getBinder() - private String getName() { - return mTransportName; - } - - // Intentionally not synchronized; same as getBinder() - private void bindIfUnbound() { - if (mBinder == null) { - Slog.d(TAG, - "Rebinding to transport " + mTransportComponent.flattenToShortString()); - bindToTransport(mTransportComponent, this); - } - } - - private long getRebindTimeout() { - final boolean isDeviceProvisioned = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0) != 0; - return isDeviceProvisioned - ? REBINDING_TIMEOUT_PROVISIONED_MS - : REBINDING_TIMEOUT_UNPROVISIONED_MS; - } - } - - public interface TransportBoundListener { - /** Should return true if this is a valid transport. */ - boolean onTransportBound(IBackupTransport binder); - } - - private class RebindOnTimeoutHandler extends Handler { - - RebindOnTimeoutHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == REBINDING_TIMEOUT_MSG) { - String componentShortString = (String) msg.obj; - ComponentName transportComponent = - ComponentName.unflattenFromString(componentShortString); - synchronized (mTransportLock) { - if (mBoundTransports.containsValue(transportComponent)) { - Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to " - + componentShortString + " so not attempting to rebind"); - return; - } - Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: " - + componentShortString); - // unbind the existing (broken) connection - TransportConnection conn = mValidTransports.get(transportComponent); - if (conn != null) { - mContext.unbindService(conn); - Slog.d(TAG, "Unbinding the existing (broken) connection to transport: " - + componentShortString); - } - } - // rebind to transport - tryBindTransport(transportComponent); - } else { - Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: " - + msg.what); - } - } - } - - private static void log_verbose(String message) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, message); - } - } - - // These only exists to make it testable with Robolectric, which is not updated to API level 24 - // yet. - // TODO: Get rid of this once Robolectric is updated. - private static ComponentName getComponentName(ServiceInfo serviceInfo) { - return new ComponentName(serviceInfo.packageName, serviceInfo.name); - } - - // These only exists to make it testable with Robolectric, which is not updated to API level 24 - // yet. - // TODO: Get rid of this once Robolectric is updated. - public static UserHandle createSystemUserHandle() { - return new UserHandle(UserHandle.USER_SYSTEM); + private static Predicate<ComponentName> fromPackageFilter(String packageName) { + return transportComponent -> packageName.equals(transportComponent.getPackageName()); } private static class TransportDescription { diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java index c2322413d2a7..cc3af8c8c933 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -907,7 +907,15 @@ public class PerformBackupTask implements BackupRestoreTask { backupData = ParcelFileDescriptor.open(mBackupDataName, ParcelFileDescriptor.MODE_READ_ONLY); backupManagerService.addBackupTrace("sending data to transport"); - int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; + + int userInitiatedFlag = + mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0; + int incrementalFlag = + mSavedStateName.length() == 0 + ? BackupTransport.FLAG_NON_INCREMENTAL + : BackupTransport.FLAG_INCREMENTAL; + int flags = userInitiatedFlag | incrementalFlag; + mStatus = transport.performBackup(mCurrentPackage, backupData, flags); } diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java new file mode 100644 index 000000000000..391ec2d7f294 --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 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.backup.transport; + +import com.android.server.backup.TransportManager; + +/** + * Listener called when a transport is registered with the {@link TransportManager}. Can be set + * using {@link TransportManager#setOnTransportRegisteredListener(OnTransportRegisteredListener)}. + */ +@FunctionalInterface +public interface OnTransportRegisteredListener { + /** + * Called when a transport is successfully registered. + * @param transportName The name of the transport. + * @param transportDirName The dir name of the transport. + */ + public void onTransportRegistered(String transportName, String transportDirName); +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java index 7bd9111028cb..bd4a0bb57072 100644 --- a/services/backup/java/com/android/server/backup/transport/TransportClient.java +++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java @@ -236,7 +236,7 @@ public class TransportClient { mBindIntent, mConnection, Context.BIND_AUTO_CREATE, - TransportManager.createSystemUserHandle()); + UserHandle.SYSTEM); if (hasBound) { // We don't need to set a time-out because we are guaranteed to get a call // back in ServiceConnection, either an onServiceConnected() or diff --git a/services/core/Android.bp b/services/core/Android.bp index 9fb26819dae0..3369458595a8 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -16,6 +16,7 @@ java_library_static { ":installd_aidl", ":storaged_aidl", ":vold_aidl", + ":mediaupdateservice_aidl", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", ], diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index af0b66daad50..04d292fa1ae4 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -293,15 +293,10 @@ public final class BatteryService extends SystemService { private void updateBatteryWarningLevelLocked() { final ContentResolver resolver = mContext.getContentResolver(); - final int defWarnLevel = mContext.getResources().getInteger( + int defWarnLevel = mContext.getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); - final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver, + mLowBatteryWarningLevel = Settings.Global.getInt(resolver, Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel); - - // NOTE: Keep the logic in sync with PowerUI.java in systemUI. - // TODO: Propagate this value from BatteryService to system UI, really. - mLowBatteryWarningLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel); - if (mLowBatteryWarningLevel == 0) { mLowBatteryWarningLevel = defWarnLevel; } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index bbba878b4ff2..77521df7e06a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -226,7 +226,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mVpns") private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>(); + // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by + // a direct call to LockdownVpnTracker.isEnabled(). + @GuardedBy("mVpns") private boolean mLockdownEnabled; + @GuardedBy("mVpns") private LockdownVpnTracker mLockdownTracker; final private Context mContext; @@ -997,9 +1001,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Network[] getVpnUnderlyingNetworks(int uid) { - if (!mLockdownEnabled) { - int user = UserHandle.getUserId(uid); - synchronized (mVpns) { + synchronized (mVpns) { + if (!mLockdownEnabled) { + int user = UserHandle.getUserId(uid); Vpn vpn = mVpns.get(user); if (vpn != null && vpn.appliesToUid(uid)) { return vpn.getUnderlyingNetworks(); @@ -1087,8 +1091,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) { state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null); } - if (mLockdownTracker != null) { - mLockdownTracker.augmentNetworkInfo(state.networkInfo); + synchronized (mVpns) { + if (mLockdownTracker != null) { + mLockdownTracker.augmentNetworkInfo(state.networkInfo); + } } } @@ -1253,8 +1259,8 @@ public class ConnectivityService extends IConnectivityManager.Stub result.put(nai.network, nc); } - if (!mLockdownEnabled) { - synchronized (mVpns) { + synchronized (mVpns) { + if (!mLockdownEnabled) { Vpn vpn = mVpns.get(userId); if (vpn != null) { Network[] networks = vpn.getUnderlyingNetworks(); @@ -1580,9 +1586,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } private Intent makeGeneralIntent(NetworkInfo info, String bcastType) { - if (mLockdownTracker != null) { - info = new NetworkInfo(info); - mLockdownTracker.augmentNetworkInfo(info); + synchronized (mVpns) { + if (mLockdownTracker != null) { + info = new NetworkInfo(info); + mLockdownTracker.augmentNetworkInfo(info); + } } Intent intent = new Intent(bcastType); @@ -3436,9 +3444,9 @@ public class ConnectivityService extends IConnectivityManager.Stub public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage, int userId) { enforceCrossUserPermission(userId); - throwIfLockdownEnabled(); synchronized (mVpns) { + throwIfLockdownEnabled(); Vpn vpn = mVpns.get(userId); if (vpn != null) { return vpn.prepare(oldPackage, newPackage); @@ -3482,9 +3490,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public ParcelFileDescriptor establishVpn(VpnConfig config) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).establish(config); } } @@ -3495,13 +3503,13 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public void startLegacyVpn(VpnProfile profile) { - throwIfLockdownEnabled(); + int user = UserHandle.getUserId(Binder.getCallingUid()); final LinkProperties egress = getActiveLinkProperties(); if (egress == null) { throw new IllegalStateException("Missing active network connection"); } - int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); } } @@ -3527,11 +3535,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public VpnInfo[] getAllVpnInfo() { enforceConnectivityInternalPermission(); - if (mLockdownEnabled) { - return new VpnInfo[0]; - } - synchronized (mVpns) { + if (mLockdownEnabled) { + return new VpnInfo[0]; + } + List<VpnInfo> infoList = new ArrayList<>(); for (int i = 0; i < mVpns.size(); i++) { VpnInfo info = createVpnInfo(mVpns.valueAt(i)); @@ -3596,33 +3604,33 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } - // Tear down existing lockdown if profile was removed - mLockdownEnabled = LockdownVpnTracker.isEnabled(); - if (mLockdownEnabled) { - byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); - if (profileTag == null) { - Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore"); - return false; - } - String profileName = new String(profileTag); - final VpnProfile profile = VpnProfile.decode( - profileName, mKeyStore.get(Credentials.VPN + profileName)); - if (profile == null) { - Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName); - setLockdownTracker(null); - return true; - } - int user = UserHandle.getUserId(Binder.getCallingUid()); - synchronized (mVpns) { + synchronized (mVpns) { + // Tear down existing lockdown if profile was removed + mLockdownEnabled = LockdownVpnTracker.isEnabled(); + if (mLockdownEnabled) { + byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN); + if (profileTag == null) { + Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore"); + return false; + } + String profileName = new String(profileTag); + final VpnProfile profile = VpnProfile.decode( + profileName, mKeyStore.get(Credentials.VPN + profileName)); + if (profile == null) { + Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName); + setLockdownTracker(null); + return true; + } + int user = UserHandle.getUserId(Binder.getCallingUid()); Vpn vpn = mVpns.get(user); if (vpn == null) { Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown"); return false; } setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile)); + } else { + setLockdownTracker(null); } - } else { - setLockdownTracker(null); } return true; @@ -3632,6 +3640,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * Internally set new {@link LockdownVpnTracker}, shutting down any existing * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. */ + @GuardedBy("mVpns") private void setLockdownTracker(LockdownVpnTracker tracker) { // Shutdown any existing tracker final LockdownVpnTracker existing = mLockdownTracker; @@ -3646,6 +3655,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @GuardedBy("mVpns") private void throwIfLockdownEnabled() { if (mLockdownEnabled) { throw new IllegalStateException("Unavailable in lockdown mode"); @@ -3693,12 +3703,12 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceConnectivityInternalPermission(); enforceCrossUserPermission(userId); - // Can't set always-on VPN if legacy VPN is already in lockdown mode. - if (LockdownVpnTracker.isEnabled()) { - return false; - } - synchronized (mVpns) { + // Can't set always-on VPN if legacy VPN is already in lockdown mode. + if (LockdownVpnTracker.isEnabled()) { + return false; + } + Vpn vpn = mVpns.get(userId); if (vpn == null) { Slog.w(TAG, "User " + userId + " has no Vpn configuration"); @@ -3874,9 +3884,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId); mVpns.put(userId, userVpn); - } - if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { - updateLockdownVpn(); + if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { + updateLockdownVpn(); + } } } @@ -3913,11 +3923,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void onUserUnlocked(int userId) { - // User present may be sent because of an unlock, which might mean an unlocked keystore. - if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { - updateLockdownVpn(); - } else { - startAlwaysOnVpn(userId); + synchronized (mVpns) { + // User present may be sent because of an unlock, which might mean an unlocked keystore. + if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) { + updateLockdownVpn(); + } else { + startAlwaysOnVpn(userId); + } } } @@ -4597,51 +4609,67 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Update the NetworkCapabilities for {@code networkAgent} to {@code networkCapabilities} - * augmented with any stateful capabilities implied from {@code networkAgent} - * (e.g., validated status and captive portal status). - * - * @param oldScore score of the network before any of the changes that prompted us - * to call this function. - * @param nai the network having its capabilities updated. - * @param networkCapabilities the new network capabilities. + * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are + * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal, + * and foreground status). */ - private void updateCapabilities( - int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) { + private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) { // Once a NetworkAgent is connected, complain if some immutable capabilities are removed. - if (nai.everConnected && !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities( - networkCapabilities)) { - // TODO: consider not complaining when a network agent degrade its capabilities if this + if (nai.everConnected && + !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) { + // TODO: consider not complaining when a network agent degrades its capabilities if this // does not cause any request (that is not a listen) currently matching that agent to // stop being matched by the updated agent. - String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities); + String diff = nai.networkCapabilities.describeImmutableDifferences(nc); if (!TextUtils.isEmpty(diff)) { Slog.wtf(TAG, "BUG: " + nai + " lost immutable capabilities:" + diff); } } // Don't modify caller's NetworkCapabilities. - networkCapabilities = new NetworkCapabilities(networkCapabilities); + NetworkCapabilities newNc = new NetworkCapabilities(nc); if (nai.lastValidated) { - networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED); + newNc.addCapability(NET_CAPABILITY_VALIDATED); } else { - networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED); + newNc.removeCapability(NET_CAPABILITY_VALIDATED); } if (nai.lastCaptivePortalDetected) { - networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } else { - networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); + newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL); } if (nai.isBackgroundNetwork()) { - networkCapabilities.removeCapability(NET_CAPABILITY_FOREGROUND); + newNc.removeCapability(NET_CAPABILITY_FOREGROUND); } else { - networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND); + newNc.addCapability(NET_CAPABILITY_FOREGROUND); } - if (Objects.equals(nai.networkCapabilities, networkCapabilities)) return; + return newNc; + } + + /** + * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically: + * + * 1. Calls mixInCapabilities to merge the passed-in NetworkCapabilities {@code nc} with the + * capabilities we manage and store in {@code nai}, such as validated status and captive + * portal status) + * 2. Takes action on the result: changes network permissions, sends CAP_CHANGED callbacks, and + * potentially triggers rematches. + * 3. Directly informs other network stack components (NetworkStatsService, VPNs, etc. of the + * change.) + * + * @param oldScore score of the network before any of the changes that prompted us + * to call this function. + * @param nai the network having its capabilities updated. + * @param nc the new network capabilities. + */ + private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) { + NetworkCapabilities newNc = mixInCapabilities(nai, nc); + + if (Objects.equals(nai.networkCapabilities, newNc)) return; final String oldPermission = getNetworkPermission(nai.networkCapabilities); - final String newPermission = getNetworkPermission(networkCapabilities); + final String newPermission = getNetworkPermission(newNc); if (!Objects.equals(oldPermission, newPermission) && nai.created && !nai.isVPN()) { try { mNetd.setNetworkPermission(nai.network.netId, newPermission); @@ -4653,11 +4681,10 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkCapabilities prevNc; synchronized (nai) { prevNc = nai.networkCapabilities; - nai.networkCapabilities = networkCapabilities; + nai.networkCapabilities = newNc; } - if (nai.getCurrentScore() == oldScore && - networkCapabilities.equalRequestableCapabilities(prevNc)) { + if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) { // If the requestable capabilities haven't changed, and the score hasn't changed, then // the change we're processing can't affect any requests, it can only affect the listens // on this network. We might have been called by rematchNetworkAndRequests when a @@ -4673,15 +4700,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // Report changes that are interesting for network statistics tracking. if (prevNc != null) { final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) != - networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED); + newNc.hasCapability(NET_CAPABILITY_NOT_METERED); final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) != - networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING); + newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); if (meteredChanged || roamingChanged) { notifyIfacesChangedForNetworkStats(); } } - if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) { + if (!newNc.hasTransport(TRANSPORT_VPN)) { // Tell VPNs about updated capabilities, since they may need to // bubble those changes through. synchronized (mVpns) { @@ -5205,11 +5232,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void notifyLockdownVpn(NetworkAgentInfo nai) { - if (mLockdownTracker != null) { - if (nai != null && nai.isVPN()) { - mLockdownTracker.onVpnStateChanged(nai.networkInfo); - } else { - mLockdownTracker.onNetworkInfoChanged(); + synchronized (mVpns) { + if (mLockdownTracker != null) { + if (nai != null && nai.isVPN()) { + mLockdownTracker.onVpnStateChanged(nai.networkInfo); + } else { + mLockdownTracker.onNetworkInfoChanged(); + } } } } @@ -5439,28 +5468,28 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public boolean addVpnAddress(String address, int prefixLength) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).addAddress(address, prefixLength); } } @Override public boolean removeVpnAddress(String address, int prefixLength) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); synchronized (mVpns) { + throwIfLockdownEnabled(); return mVpns.get(user).removeAddress(address, prefixLength); } } @Override public boolean setUnderlyingNetworksForVpn(Network[] networks) { - throwIfLockdownEnabled(); int user = UserHandle.getUserId(Binder.getCallingUid()); - boolean success; + final boolean success; synchronized (mVpns) { + throwIfLockdownEnabled(); success = mVpns.get(user).setUnderlyingNetworks(networks); } if (success) { @@ -5520,31 +5549,31 @@ public class ConnectivityService extends IConnectivityManager.Stub setAlwaysOnVpnPackage(userId, null, false); setVpnPackageAuthorization(alwaysOnPackage, userId, false); } - } - // Turn Always-on VPN off - if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { - final long ident = Binder.clearCallingIdentity(); - try { - mKeyStore.delete(Credentials.LOCKDOWN_VPN); - mLockdownEnabled = false; - setLockdownTracker(null); - } finally { - Binder.restoreCallingIdentity(ident); + // Turn Always-on VPN off + if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { + final long ident = Binder.clearCallingIdentity(); + try { + mKeyStore.delete(Credentials.LOCKDOWN_VPN); + mLockdownEnabled = false; + setLockdownTracker(null); + } finally { + Binder.restoreCallingIdentity(ident); + } } - } - // Turn VPN off - VpnConfig vpnConfig = getVpnConfig(userId); - if (vpnConfig != null) { - if (vpnConfig.legacy) { - prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); - } else { - // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections - // in the future without user intervention. - setVpnPackageAuthorization(vpnConfig.user, userId, false); + // Turn VPN off + VpnConfig vpnConfig = getVpnConfig(userId); + if (vpnConfig != null) { + if (vpnConfig.legacy) { + prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); + } else { + // Prevent this app (packagename = vpnConfig.user) from initiating + // VPN connections in the future without user intervention. + setVpnPackageAuthorization(vpnConfig.user, userId, false); - prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + prepareVpn(null, VpnConfig.LEGACY_VPN, userId); + } } } } diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 02cfe3dc75e5..9d228c3d0a6b 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -25,6 +25,7 @@ import static android.system.OsConstants.SOCK_DGRAM; import static com.android.internal.util.Preconditions.checkNotNull; import android.content.Context; +import android.net.ConnectivityManager; import android.net.IIpSecService; import android.net.INetd; import android.net.IpSecAlgorithm; @@ -62,7 +63,6 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import libcore.io.IoUtils; @@ -83,7 +83,7 @@ public class IpSecService extends IIpSecService.Stub { private static final String NETD_SERVICE_NAME = "netd"; private static final int[] DIRECTIONS = - new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}; + new int[] {IpSecManager.DIRECTION_OUT, IpSecManager.DIRECTION_IN}; private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms private static final int MAX_PORT_BIND_ATTEMPTS = 10; @@ -104,10 +104,10 @@ public class IpSecService extends IIpSecService.Stub { private final Context mContext; /** - * The next non-repeating global ID for tracking resources between users, this service, - * and kernel data structures. Accessing this variable is not thread safe, so it is - * only read or modified within blocks synchronized on IpSecService.this. We want to - * avoid -1 (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it). + * The next non-repeating global ID for tracking resources between users, this service, and + * kernel data structures. Accessing this variable is not thread safe, so it is only read or + * modified within blocks synchronized on IpSecService.this. We want to avoid -1 + * (INVALID_RESOURCE_ID) and 0 (we probably forgot to initialize it). */ @GuardedBy("IpSecService.this") private int mNextResourceId = 1; @@ -536,14 +536,14 @@ public class IpSecService extends IIpSecService.Stub { private final class TransformRecord extends KernelResourceRecord { private final IpSecConfig mConfig; - private final SpiRecord[] mSpis; + private final SpiRecord mSpi; private final EncapSocketRecord mSocket; TransformRecord( - int resourceId, IpSecConfig config, SpiRecord[] spis, EncapSocketRecord socket) { + int resourceId, IpSecConfig config, SpiRecord spi, EncapSocketRecord socket) { super(resourceId); mConfig = config; - mSpis = spis; + mSpi = spi; mSocket = socket; } @@ -551,29 +551,26 @@ public class IpSecService extends IIpSecService.Stub { return mConfig; } - public SpiRecord getSpiRecord(int direction) { - return mSpis[direction]; + public SpiRecord getSpiRecord() { + return mSpi; } /** always guarded by IpSecService#this */ @Override public void freeUnderlyingResources() { - for (int direction : DIRECTIONS) { - int spi = mSpis[direction].getSpi(); - try { - mSrvConfig - .getNetdInstance() - .ipSecDeleteSecurityAssociation( - mResourceId, - direction, - mConfig.getLocalAddress(), - mConfig.getRemoteAddress(), - spi); - } catch (ServiceSpecificException e) { - // FIXME: get the error code and throw is at an IOException from Errno Exception - } catch (RemoteException e) { - Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); - } + int spi = mSpi.getSpi(); + try { + mSrvConfig + .getNetdInstance() + .ipSecDeleteSecurityAssociation( + mResourceId, + mConfig.getSourceAddress(), + mConfig.getDestinationAddress(), + spi); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + } catch (RemoteException e) { + Log.e(TAG, "Failed to delete SA with ID: " + mResourceId); } getResourceTracker().give(); @@ -597,10 +594,8 @@ public class IpSecService extends IIpSecService.Stub { .append(super.toString()) .append(", mSocket=") .append(mSocket) - .append(", mSpis[OUT].mResourceId=") - .append(mSpis[IpSecTransform.DIRECTION_OUT].mResourceId) - .append(", mSpis[IN].mResourceId=") - .append(mSpis[IpSecTransform.DIRECTION_IN].mResourceId) + .append(", mSpi.mResourceId=") + .append(mSpi.mResourceId) .append(", mConfig=") .append(mConfig) .append("}"); @@ -609,23 +604,16 @@ public class IpSecService extends IIpSecService.Stub { } private final class SpiRecord extends KernelResourceRecord { - private final int mDirection; - private final String mLocalAddress; - private final String mRemoteAddress; + private final String mSourceAddress; + private final String mDestinationAddress; private int mSpi; private boolean mOwnedByTransform = false; - SpiRecord( - int resourceId, - int direction, - String localAddress, - String remoteAddress, - int spi) { + SpiRecord(int resourceId, String sourceAddress, String destinationAddress, int spi) { super(resourceId); - mDirection = direction; - mLocalAddress = localAddress; - mRemoteAddress = remoteAddress; + mSourceAddress = sourceAddress; + mDestinationAddress = destinationAddress; mSpi = spi; } @@ -646,7 +634,7 @@ public class IpSecService extends IIpSecService.Stub { mSrvConfig .getNetdInstance() .ipSecDeleteSecurityAssociation( - mResourceId, mDirection, mLocalAddress, mRemoteAddress, mSpi); + mResourceId, mSourceAddress, mDestinationAddress, mSpi); } catch (ServiceSpecificException e) { // FIXME: get the error code and throw is at an IOException from Errno Exception } catch (RemoteException e) { @@ -662,6 +650,10 @@ public class IpSecService extends IIpSecService.Stub { return mSpi; } + public String getDestinationAddress() { + return mDestinationAddress; + } + public void setOwnedByTransform() { if (mOwnedByTransform) { // Programming error @@ -689,12 +681,10 @@ public class IpSecService extends IIpSecService.Stub { .append(super.toString()) .append(", mSpi=") .append(mSpi) - .append(", mDirection=") - .append(mDirection) - .append(", mLocalAddress=") - .append(mLocalAddress) - .append(", mRemoteAddress=") - .append(mRemoteAddress) + .append(", mSourceAddress=") + .append(mSourceAddress) + .append(", mDestinationAddress=") + .append(mDestinationAddress) .append(", mOwnedByTransform=") .append(mOwnedByTransform) .append("}"); @@ -772,14 +762,17 @@ public class IpSecService extends IIpSecService.Stub { /** @hide */ @VisibleForTesting public IpSecService(Context context, IpSecServiceConfiguration config) { - this(context, config, (fd, uid) -> { - try{ - TrafficStats.setThreadStatsUid(uid); - TrafficStats.tagFileDescriptor(fd); - } finally { - TrafficStats.clearThreadStatsUid(); - } - }); + this( + context, + config, + (fd, uid) -> { + try { + TrafficStats.setThreadStatsUid(uid); + TrafficStats.tagFileDescriptor(fd); + } finally { + TrafficStats.clearThreadStatsUid(); + } + }); } /** @hide */ @@ -845,8 +838,8 @@ public class IpSecService extends IIpSecService.Stub { */ private static void checkDirection(int direction) { switch (direction) { - case IpSecTransform.DIRECTION_OUT: - case IpSecTransform.DIRECTION_IN: + case IpSecManager.DIRECTION_OUT: + case IpSecManager.DIRECTION_IN: return; } throw new IllegalArgumentException("Invalid Direction: " + direction); @@ -855,10 +848,8 @@ public class IpSecService extends IIpSecService.Stub { /** Get a new SPI and maintain the reservation in the system server */ @Override public synchronized IpSecSpiResponse allocateSecurityParameterIndex( - int direction, String remoteAddress, int requestedSpi, IBinder binder) - throws RemoteException { - checkDirection(direction); - checkInetAddress(remoteAddress); + String destinationAddress, int requestedSpi, IBinder binder) throws RemoteException { + checkInetAddress(destinationAddress); /* requestedSpi can be anything in the int range, so no check is needed. */ checkNotNull(binder, "Null Binder passed to allocateSecurityParameterIndex"); @@ -866,28 +857,21 @@ public class IpSecService extends IIpSecService.Stub { final int resourceId = mNextResourceId++; int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX; - String localAddress = ""; - try { if (!userRecord.mSpiQuotaTracker.isAvailable()) { return new IpSecSpiResponse( IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi); } + spi = mSrvConfig .getNetdInstance() - .ipSecAllocateSpi( - resourceId, - direction, - localAddress, - remoteAddress, - requestedSpi); + .ipSecAllocateSpi(resourceId, "", destinationAddress, requestedSpi); Log.d(TAG, "Allocated SPI " + spi); userRecord.mSpiRecords.put( resourceId, new RefcountedResource<SpiRecord>( - new SpiRecord(resourceId, direction, localAddress, remoteAddress, spi), - binder)); + new SpiRecord(resourceId, "", destinationAddress, spi), binder)); } catch (ServiceSpecificException e) { // TODO: Add appropriate checks when other ServiceSpecificException types are supported return new IpSecSpiResponse( @@ -1032,27 +1016,27 @@ public class IpSecService extends IIpSecService.Stub { } @VisibleForTesting - void validateAlgorithms(IpSecConfig config, int direction) throws IllegalArgumentException { - IpSecAlgorithm auth = config.getAuthentication(direction); - IpSecAlgorithm crypt = config.getEncryption(direction); - IpSecAlgorithm aead = config.getAuthenticatedEncryption(direction); - - // Validate the algorithm set - Preconditions.checkArgument( - aead != null || crypt != null || auth != null, - "No Encryption or Authentication algorithms specified"); - Preconditions.checkArgument( - auth == null || auth.isAuthentication(), - "Unsupported algorithm for Authentication"); - Preconditions.checkArgument( + void validateAlgorithms(IpSecConfig config) throws IllegalArgumentException { + IpSecAlgorithm auth = config.getAuthentication(); + IpSecAlgorithm crypt = config.getEncryption(); + IpSecAlgorithm aead = config.getAuthenticatedEncryption(); + + // Validate the algorithm set + Preconditions.checkArgument( + aead != null || crypt != null || auth != null, + "No Encryption or Authentication algorithms specified"); + Preconditions.checkArgument( + auth == null || auth.isAuthentication(), + "Unsupported algorithm for Authentication"); + Preconditions.checkArgument( crypt == null || crypt.isEncryption(), "Unsupported algorithm for Encryption"); - Preconditions.checkArgument( - aead == null || aead.isAead(), - "Unsupported algorithm for Authenticated Encryption"); - Preconditions.checkArgument( - aead == null || (auth == null && crypt == null), - "Authenticated Encryption is mutually exclusive with other Authentication " - + "or Encryption algorithms"); + Preconditions.checkArgument( + aead == null || aead.isAead(), + "Unsupported algorithm for Authenticated Encryption"); + Preconditions.checkArgument( + aead == null || (auth == null && crypt == null), + "Authenticated Encryption is mutually exclusive with other Authentication " + + "or Encryption algorithms"); } /** @@ -1062,29 +1046,6 @@ public class IpSecService extends IIpSecService.Stub { private void checkIpSecConfig(IpSecConfig config) { UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); - if (config.getLocalAddress() == null) { - throw new IllegalArgumentException("Invalid null Local InetAddress"); - } - - if (config.getRemoteAddress() == null) { - throw new IllegalArgumentException("Invalid null Remote InetAddress"); - } - - switch (config.getMode()) { - case IpSecTransform.MODE_TRANSPORT: - if (!config.getLocalAddress().isEmpty()) { - throw new IllegalArgumentException("Non-empty Local Address"); - } - // Must be valid, and not a wildcard - checkInetAddress(config.getRemoteAddress()); - break; - case IpSecTransform.MODE_TUNNEL: - break; - default: - throw new IllegalArgumentException( - "Invalid IpSecTransform.mode: " + config.getMode()); - } - switch (config.getEncapType()) { case IpSecTransform.ENCAP_NONE: break; @@ -1103,11 +1064,36 @@ public class IpSecService extends IIpSecService.Stub { throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType()); } - for (int direction : DIRECTIONS) { - validateAlgorithms(config, direction); + validateAlgorithms(config); - // Retrieve SPI record; will throw IllegalArgumentException if not found - userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId(direction)); + // Retrieve SPI record; will throw IllegalArgumentException if not found + SpiRecord s = userRecord.mSpiRecords.getResourceOrThrow(config.getSpiResourceId()); + + // If no remote address is supplied, then use one from the SPI. + if (TextUtils.isEmpty(config.getDestinationAddress())) { + config.setDestinationAddress(s.getDestinationAddress()); + } + + // All remote addresses must match + if (!config.getDestinationAddress().equals(s.getDestinationAddress())) { + throw new IllegalArgumentException("Mismatched remote addresseses."); + } + + // This check is technically redundant due to the chain of custody between the SPI and + // the IpSecConfig, but in the future if the dest is allowed to be set explicitly in + // the transform, this will prevent us from messing up. + checkInetAddress(config.getDestinationAddress()); + + // Require a valid source address for all transforms. + checkInetAddress(config.getSourceAddress()); + + switch (config.getMode()) { + case IpSecTransform.MODE_TRANSPORT: + case IpSecTransform.MODE_TUNNEL: + break; + default: + throw new IllegalArgumentException( + "Invalid IpSecTransform.mode: " + config.getMode()); } } @@ -1127,13 +1113,12 @@ public class IpSecService extends IIpSecService.Stub { UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); - // Avoid resizing by creating a dependency array of min-size 3 (1 UDP encap + 2 SPIs) - List<RefcountedResource> dependencies = new ArrayList<>(3); + // Avoid resizing by creating a dependency array of min-size 2 (1 UDP encap + 1 SPI) + List<RefcountedResource> dependencies = new ArrayList<>(2); if (!userRecord.mTransformQuotaTracker.isAvailable()) { return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } - SpiRecord[] spis = new SpiRecord[DIRECTIONS.length]; int encapType, encapLocalPort = 0, encapRemotePort = 0; EncapSocketRecord socketRecord = null; @@ -1149,51 +1134,46 @@ public class IpSecService extends IIpSecService.Stub { encapRemotePort = c.getEncapRemotePort(); } - for (int direction : DIRECTIONS) { - IpSecAlgorithm auth = c.getAuthentication(direction); - IpSecAlgorithm crypt = c.getEncryption(direction); - IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction); + IpSecAlgorithm auth = c.getAuthentication(); + IpSecAlgorithm crypt = c.getEncryption(); + IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(); - RefcountedResource<SpiRecord> refcountedSpiRecord = - userRecord.mSpiRecords.getRefcountedResourceOrThrow( - c.getSpiResourceId(direction)); - dependencies.add(refcountedSpiRecord); + RefcountedResource<SpiRecord> refcountedSpiRecord = + userRecord.mSpiRecords.getRefcountedResourceOrThrow(c.getSpiResourceId()); + dependencies.add(refcountedSpiRecord); + SpiRecord spiRecord = refcountedSpiRecord.getResource(); - spis[direction] = refcountedSpiRecord.getResource(); - int spi = spis[direction].getSpi(); - try { - mSrvConfig - .getNetdInstance() - .ipSecAddSecurityAssociation( - resourceId, - c.getMode(), - direction, - c.getLocalAddress(), - c.getRemoteAddress(), - (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0, - spi, - (auth != null) ? auth.getName() : "", - (auth != null) ? auth.getKey() : new byte[] {}, - (auth != null) ? auth.getTruncationLengthBits() : 0, - (crypt != null) ? crypt.getName() : "", - (crypt != null) ? crypt.getKey() : new byte[] {}, - (crypt != null) ? crypt.getTruncationLengthBits() : 0, - (authCrypt != null) ? authCrypt.getName() : "", - (authCrypt != null) ? authCrypt.getKey() : new byte[] {}, - (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0, - encapType, - encapLocalPort, - encapRemotePort); - } catch (ServiceSpecificException e) { - // FIXME: get the error code and throw is at an IOException from Errno Exception - return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); - } + try { + mSrvConfig + .getNetdInstance() + .ipSecAddSecurityAssociation( + resourceId, + c.getMode(), + c.getSourceAddress(), + c.getDestinationAddress(), + (c.getNetwork() != null) ? c.getNetwork().netId : 0, + spiRecord.getSpi(), + (auth != null) ? auth.getName() : "", + (auth != null) ? auth.getKey() : new byte[] {}, + (auth != null) ? auth.getTruncationLengthBits() : 0, + (crypt != null) ? crypt.getName() : "", + (crypt != null) ? crypt.getKey() : new byte[] {}, + (crypt != null) ? crypt.getTruncationLengthBits() : 0, + (authCrypt != null) ? authCrypt.getName() : "", + (authCrypt != null) ? authCrypt.getKey() : new byte[] {}, + (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0, + encapType, + encapLocalPort, + encapRemotePort); + } catch (ServiceSpecificException e) { + // FIXME: get the error code and throw is at an IOException from Errno Exception + return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } // Both SAs were created successfully, time to construct a record and lock it away userRecord.mTransformRecords.put( resourceId, new RefcountedResource<TransformRecord>( - new TransformRecord(resourceId, c, spis, socketRecord), + new TransformRecord(resourceId, c, spiRecord, socketRecord), binder, dependencies.toArray(new RefcountedResource[dependencies.size()]))); return new IpSecTransformResponse(IpSecManager.Status.OK, resourceId); @@ -1217,9 +1197,9 @@ public class IpSecService extends IIpSecService.Stub { */ @Override public synchronized void applyTransportModeTransform( - ParcelFileDescriptor socket, int resourceId) throws RemoteException { + ParcelFileDescriptor socket, int direction, int resourceId) throws RemoteException { UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); - + checkDirection(direction); // Get transform record; if no transform is found, will throw IllegalArgumentException TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId); @@ -1230,17 +1210,15 @@ public class IpSecService extends IIpSecService.Stub { IpSecConfig c = info.getConfig(); try { - for (int direction : DIRECTIONS) { - mSrvConfig - .getNetdInstance() - .ipSecApplyTransportModeTransform( - socket.getFileDescriptor(), - resourceId, - direction, - c.getLocalAddress(), - c.getRemoteAddress(), - info.getSpiRecord(direction).getSpi()); - } + mSrvConfig + .getNetdInstance() + .ipSecApplyTransportModeTransform( + socket.getFileDescriptor(), + resourceId, + direction, + c.getSourceAddress(), + c.getDestinationAddress(), + info.getSpiRecord().getSpi()); } catch (ServiceSpecificException e) { if (e.errorCode == EINVAL) { throw new IllegalArgumentException(e.toString()); @@ -1251,14 +1229,14 @@ public class IpSecService extends IIpSecService.Stub { } /** - * Remove a transport mode transform from a socket, applying the default (empty) policy. This - * will ensure that NO IPsec policy is applied to the socket (would be the equivalent of - * applying a policy that performs no IPsec). Today the resourceId parameter is passed but not - * used: reserved for future improved input validation. + * Remove transport mode transforms from a socket, applying the default (empty) policy. This + * ensures that NO IPsec policy is applied to the socket (would be the equivalent of applying a + * policy that performs no IPsec). Today the resourceId parameter is passed but not used: + * reserved for future improved input validation. */ @Override - public synchronized void removeTransportModeTransform(ParcelFileDescriptor socket, int resourceId) - throws RemoteException { + public synchronized void removeTransportModeTransforms( + ParcelFileDescriptor socket, int resourceId) throws RemoteException { try { mSrvConfig .getNetdInstance() diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3dc237db137e..dfe89e00b20b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -397,6 +397,7 @@ import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; import com.android.internal.policy.IKeyguardDismissCallback; +import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; @@ -6242,7 +6243,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Clear its pending alarms AlarmManagerInternal ami = LocalServices.getService(AlarmManagerInternal.class); - ami.removeAlarmsForUid(uid); + ami.removeAlarmsForUid(appInfo.uid); } } catch (RemoteException e) { } @@ -7260,15 +7261,22 @@ public class ActivityManagerService extends IActivityManager.Stub } ProfilerInfo profilerInfo = null; - String agent = null; + String preBindAgent = null; if (mProfileApp != null && mProfileApp.equals(processName)) { mProfileProc = app; - profilerInfo = (mProfilerInfo != null && mProfilerInfo.profileFile != null) ? - new ProfilerInfo(mProfilerInfo) : null; - agent = mProfilerInfo != null ? mProfilerInfo.agent : null; + if (mProfilerInfo != null) { + // Send a profiler info object to the app if either a file is given, or + // an agent should be loaded at bind-time. + boolean needsInfo = mProfilerInfo.profileFile != null + || mProfilerInfo.attachAgentDuringBind; + profilerInfo = needsInfo ? new ProfilerInfo(mProfilerInfo) : null; + if (!mProfilerInfo.attachAgentDuringBind) { + preBindAgent = mProfilerInfo.agent; + } + } } else if (app.instr != null && app.instr.mProfileFile != null) { profilerInfo = new ProfilerInfo(app.instr.mProfileFile, null, 0, false, false, - null); + null, false); } boolean enableTrackAllocation = false; @@ -7337,8 +7345,8 @@ public class ActivityManagerService extends IActivityManager.Stub // If we were asked to attach an agent on startup, do so now, before we're binding // application code. - if (agent != null) { - thread.attachAgent(agent); + if (preBindAgent != null) { + thread.attachAgent(preBindAgent); } checkTime(startTime, "attachApplicationLocked: immediately before bindApplication"); @@ -8386,21 +8394,11 @@ public class ActivityManagerService extends IActivityManager.Stub // entering picture-in-picture (this will prompt the user to authenticate if the // device is currently locked). try { - dismissKeyguard(token, new IKeyguardDismissCallback.Stub() { - @Override - public void onDismissError() throws RemoteException { - // Do nothing - } - + dismissKeyguard(token, new KeyguardDismissCallback() { @Override public void onDismissSucceeded() throws RemoteException { mHandler.post(enterPipRunnable); } - - @Override - public void onDismissCancelled() throws RemoteException { - // Do nothing - } }, null /* message */); } catch (RemoteException e) { // Local call @@ -25345,6 +25343,10 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + if (updateFrameworkRes && mWindowManager != null) { + ActivityThread.currentActivityThread().getExecutor().execute( + mWindowManager::onOverlayChanged); + } } /** diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 4f60e1731a50..1240f5e664aa 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -109,6 +109,7 @@ final class ActivityManagerShellCommand extends ShellCommand { private boolean mAutoStop; private boolean mStreaming; // Streaming the profiling output to a file. private String mAgent; // Agent to attach on startup. + private boolean mAttachAgentDuringBind; // Whether agent should be attached late. private int mDisplayId; private int mWindowingMode; private int mActivityType; @@ -296,7 +297,21 @@ final class ActivityManagerShellCommand extends ShellCommand { } else if (opt.equals("--streaming")) { mStreaming = true; } else if (opt.equals("--attach-agent")) { + if (mAgent != null) { + cmd.getErrPrintWriter().println( + "Multiple --attach-agent(-bind) not supported"); + return false; + } + mAgent = getNextArgRequired(); + mAttachAgentDuringBind = false; + } else if (opt.equals("--attach-agent-bind")) { + if (mAgent != null) { + cmd.getErrPrintWriter().println( + "Multiple --attach-agent(-bind) not supported"); + return false; + } mAgent = getNextArgRequired(); + mAttachAgentDuringBind = true; } else if (opt.equals("-R")) { mRepeat = Integer.parseInt(getNextArgRequired()); } else if (opt.equals("-S")) { @@ -384,7 +399,7 @@ final class ActivityManagerShellCommand extends ShellCommand { } } profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop, - mStreaming, mAgent); + mStreaming, mAgent, mAttachAgentDuringBind); } pw.println("Starting: " + intent); @@ -766,7 +781,7 @@ final class ActivityManagerShellCommand extends ShellCommand { return -1; } profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming, - null); + null, false); } try { @@ -2544,6 +2559,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" (use with --start-profiler)"); pw.println(" -P <FILE>: like above, but profiling stops when app goes idle"); pw.println(" --attach-agent <agent>: attach the given agent before binding"); + pw.println(" --attach-agent-bind <agent>: attach the given agent during binding"); pw.println(" -R: repeat the activity launch <COUNT> times. Prior to each repeat,"); pw.println(" the top activity will be finished."); pw.println(" -S: force stop the target app before starting the activity"); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index b74c8da611a8..b5fbee617fbd 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1609,9 +1609,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mStackSupervisor.mStoppingActivities.remove(this); mStackSupervisor.mGoingToSleepActivities.remove(this); - // If an activity is not in the paused state when becoming visible, cycle to the paused - // state. - if (state != PAUSED) { + // If the activity is stopped or stopping, cycle to the paused state. + if (state == STOPPED || state == STOPPING) { // An activity must be in the {@link PAUSING} state for the system to validate // the move to {@link PAUSED}. state = PAUSING; diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java index 7d3b670bdfa3..10e6cad70bcd 100644 --- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java @@ -25,6 +25,7 @@ import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; import android.os.ParcelableException; +import android.os.RemoteException; import android.util.Slog; import com.android.server.SystemService; @@ -86,7 +87,7 @@ public class BroadcastRadioService extends SystemService { @Override public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig, - boolean withAudio, ITunerCallback callback) { + boolean withAudio, ITunerCallback callback) throws RemoteException { Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)"); enforcePolicyAccess(); if (callback == null) { diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java index e5090ed5b715..f9b35f53d4d5 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java @@ -21,6 +21,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.os.IBinder; @@ -249,8 +250,7 @@ class Tuner extends ITuner.Stub { } } - @Override - public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { + List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { Map<String, String> sFilter = vendorFilter; synchronized (mLock) { checkNotClosedLocked(); @@ -263,6 +263,16 @@ class Tuner extends ITuner.Stub { } @Override + public void startProgramListUpdates(ProgramList.Filter filter) { + mTunerCallback.startProgramListUpdates(filter); + } + + @Override + public void stopProgramListUpdates() { + mTunerCallback.stopProgramListUpdates(); + } + + @Override public boolean isConfigFlagSupported(int flag) { return flag == RadioManager.CONFIG_FORCE_ANALOG; } diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java index 673ff88d5c98..18f56ed58475 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java @@ -19,6 +19,7 @@ package com.android.server.broadcastradio.hal1; import android.annotation.NonNull; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; +import android.hardware.radio.ProgramList; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; import android.hardware.radio.RadioTuner; @@ -28,6 +29,10 @@ import android.util.Slog; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; class TunerCallback implements ITunerCallback { private static final String TAG = "BroadcastRadioService.TunerCallback"; @@ -40,6 +45,8 @@ class TunerCallback implements ITunerCallback { @NonNull private final Tuner mTuner; @NonNull private final ITunerCallback mClientCallback; + private final AtomicReference<ProgramList.Filter> mProgramListFilter = new AtomicReference<>(); + TunerCallback(@NonNull Tuner tuner, @NonNull ITunerCallback clientCallback, int halRev) { mTuner = tuner; mClientCallback = clientCallback; @@ -78,6 +85,15 @@ class TunerCallback implements ITunerCallback { mTuner.close(); } + void startProgramListUpdates(@NonNull ProgramList.Filter filter) { + mProgramListFilter.set(Objects.requireNonNull(filter)); + sendProgramListUpdate(); + } + + void stopProgramListUpdates() { + mProgramListFilter.set(null); + } + @Override public void onError(int status) { dispatch(() -> mClientCallback.onError(status)); @@ -121,6 +137,28 @@ class TunerCallback implements ITunerCallback { @Override public void onProgramListChanged() { dispatch(() -> mClientCallback.onProgramListChanged()); + sendProgramListUpdate(); + } + + private void sendProgramListUpdate() { + ProgramList.Filter filter = mProgramListFilter.get(); + if (filter == null) return; + + List<RadioManager.ProgramInfo> modified; + try { + modified = mTuner.getProgramList(filter.getVendorFilter()); + } catch (IllegalStateException ex) { + Slog.d(TAG, "Program list not ready yet"); + return; + } + Set<RadioManager.ProgramInfo> modifiedSet = modified.stream().collect(Collectors.toSet()); + ProgramList.Chunk chunk = new ProgramList.Chunk(true, true, modifiedSet, null); + dispatch(() -> mClientCallback.onProgramListUpdated(chunk)); + } + + @Override + public void onProgramListUpdated(ProgramList.Chunk chunk) { + dispatch(() -> mClientCallback.onProgramListUpdated(chunk)); } @Override diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 9158ff0cb66f..fc9a5d61704d 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -82,7 +82,7 @@ public class BroadcastRadioService { } public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, - boolean withAudio, @NonNull ITunerCallback callback) { + boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException { Objects.requireNonNull(callback); if (!withAudio) { diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java index 2c129bbac33e..60a927c56959 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java @@ -20,15 +20,22 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.broadcastradio.V2_0.AmFmBandRange; import android.hardware.broadcastradio.V2_0.AmFmRegionConfig; +import android.hardware.broadcastradio.V2_0.ProgramFilter; +import android.hardware.broadcastradio.V2_0.ProgramIdentifier; +import android.hardware.broadcastradio.V2_0.ProgramInfo; +import android.hardware.broadcastradio.V2_0.ProgramInfoFlags; +import android.hardware.broadcastradio.V2_0.ProgramListChunk; import android.hardware.broadcastradio.V2_0.Properties; import android.hardware.broadcastradio.V2_0.Result; import android.hardware.broadcastradio.V2_0.VendorKeyValue; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.os.ParcelableException; import android.util.Slog; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -36,6 +43,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; class Convert { private static final String TAG = "BcRadio2Srv.convert"; @@ -78,43 +86,52 @@ class Convert { return map; } + private static @ProgramSelector.ProgramType int identifierTypeToProgramType( + @ProgramSelector.IdentifierType int idType) { + switch (idType) { + case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY: + case ProgramSelector.IDENTIFIER_TYPE_RDS_PI: + // TODO(b/69958423): verify AM/FM with frequency range + return ProgramSelector.PROGRAM_TYPE_FM; + case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT: + // TODO(b/69958423): verify AM/FM with frequency range + return ProgramSelector.PROGRAM_TYPE_FM_HD; + case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC: + case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: + case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: + case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: + return ProgramSelector.PROGRAM_TYPE_DAB; + case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: + case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: + return ProgramSelector.PROGRAM_TYPE_DRMO; + case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID: + case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL: + return ProgramSelector.PROGRAM_TYPE_SXM; + } + if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START + && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) { + return idType; + } + return ProgramSelector.PROGRAM_TYPE_INVALID; + } + private static @NonNull int[] identifierTypesToProgramTypes(@NonNull int[] idTypes) { Set<Integer> pTypes = new HashSet<>(); for (int idType : idTypes) { - switch (idType) { - case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY: - case ProgramSelector.IDENTIFIER_TYPE_RDS_PI: - // TODO(b/69958423): verify AM/FM with region info - pTypes.add(ProgramSelector.PROGRAM_TYPE_AM); - pTypes.add(ProgramSelector.PROGRAM_TYPE_FM); - break; - case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT: - // TODO(b/69958423): verify AM/FM with region info - pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD); - pTypes.add(ProgramSelector.PROGRAM_TYPE_FM_HD); - break; - case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC: - case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE: - case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID: - case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY: - pTypes.add(ProgramSelector.PROGRAM_TYPE_DAB); - break; - case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID: - case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY: - pTypes.add(ProgramSelector.PROGRAM_TYPE_DRMO); - break; - case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID: - case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL: - pTypes.add(ProgramSelector.PROGRAM_TYPE_SXM); - break; - default: - break; + int pType = identifierTypeToProgramType(idType); + + if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue; + + pTypes.add(pType); + if (pType == ProgramSelector.PROGRAM_TYPE_FM) { + // TODO(b/69958423): verify AM/FM with region info + pTypes.add(ProgramSelector.PROGRAM_TYPE_AM); } - if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START - && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) { - pTypes.add(idType); + if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) { + // TODO(b/69958423): verify AM/FM with region info + pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD); } } @@ -189,6 +206,64 @@ class Convert { false, // isBgScanSupported is deprecated supportedProgramTypes, supportedIdentifierTypes, - vendorInfoFromHal(prop.vendorInfo)); + vendorInfoFromHal(prop.vendorInfo) + ); + } + + static @NonNull ProgramIdentifier programIdentifierToHal( + @NonNull ProgramSelector.Identifier id) { + ProgramIdentifier hwId = new ProgramIdentifier(); + hwId.type = id.getType(); + hwId.value = id.getValue(); + return hwId; + } + + static @NonNull ProgramSelector.Identifier programIdentifierFromHal(@NonNull ProgramIdentifier id) { + return new ProgramSelector.Identifier(id.type, id.value); + } + + static @NonNull ProgramSelector programSelectorFromHal( + @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) { + ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().map( + id -> programIdentifierFromHal(id)).toArray(ProgramSelector.Identifier[]::new); + + return new ProgramSelector( + identifierTypeToProgramType(sel.primaryId.type), + programIdentifierFromHal(sel.primaryId), + secondaryIds, null); + } + + static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) { + return new RadioManager.ProgramInfo( + programSelectorFromHal(info.selector), + (info.infoFlags & ProgramInfoFlags.TUNED) != 0, + (info.infoFlags & ProgramInfoFlags.STEREO) != 0, + false, // TODO(b/69860743): digital + info.signalQuality, + null, // TODO(b/69860743): metadata + info.infoFlags, + vendorInfoFromHal(info.vendorInfo) + ); + } + + static @NonNull ProgramFilter programFilterToHal(@NonNull ProgramList.Filter filter) { + ProgramFilter hwFilter = new ProgramFilter(); + + filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add); + filter.getIdentifiers().stream().forEachOrdered( + id -> hwFilter.identifiers.add(programIdentifierToHal(id))); + hwFilter.includeCategories = filter.areCategoriesIncluded(); + hwFilter.excludeModifications = filter.areModificationsExcluded(); + + return hwFilter; + } + + static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) { + Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().map( + info -> programInfoFromHal(info)).collect(Collectors.toSet()); + Set<ProgramSelector.Identifier> removed = chunk.removed.stream().map( + id -> programIdentifierFromHal(id)).collect(Collectors.toSet()); + + return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed); } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index 45b21900ff33..c8e15c1a2f4f 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -63,20 +63,16 @@ class RadioModule { } } - public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) { + public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) + throws RemoteException { TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb)); Mutable<ITunerSession> hwSession = new Mutable<>(); MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR); - try { - mService.openSession(cb, (int result, ITunerSession session) -> { - hwSession.value = session; - halResult.value = result; - }); - } catch (RemoteException ex) { - Slog.e(TAG, "failed to open session", ex); - throw new ParcelableException(ex); - } + mService.openSession(cb, (int result, ITunerSession session) -> { + hwSession.value = session; + halResult.value = result; + }); Convert.throwOnError("openSession", halResult.value); Objects.requireNonNull(hwSession.value); diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java index c9084ee9517a..ed2a1b3ce851 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java @@ -56,7 +56,9 @@ class TunerCallback extends ITunerCallback.Stub { public void onCurrentProgramInfoChanged(ProgramInfo info) {} @Override - public void onProgramListUpdated(ProgramListChunk chunk) {} + public void onProgramListUpdated(ProgramListChunk chunk) { + dispatch(() -> mClientCb.onProgramListUpdated(Convert.programListChunkFromHal(chunk))); + } @Override public void onAntennaStateChange(boolean connected) {} diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java index 8ed646af2b88..e093c9df7c4b 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -22,6 +22,7 @@ import android.hardware.broadcastradio.V2_0.ConfigFlag; import android.hardware.broadcastradio.V2_0.ITunerSession; import android.hardware.broadcastradio.V2_0.Result; import android.hardware.radio.ITuner; +import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.media.AudioSystem; @@ -184,10 +185,19 @@ class TunerSession extends ITuner.Stub { } @Override - public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) { + public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException { synchronized (mLock) { checkNotClosedLocked(); - return null; + int halResult = mHwSession.startProgramListUpdates(Convert.programFilterToHal(filter)); + Convert.throwOnError("startProgramListUpdates", halResult); + } + } + + @Override + public void stopProgramListUpdates() throws RemoteException { + synchronized (mLock) { + checkNotClosedLocked(); + mHwSession.stopProgramListUpdates(); } } @@ -226,17 +236,12 @@ class TunerSession extends ITuner.Stub { } @Override - public void setConfigFlag(int flag, boolean value) { + public void setConfigFlag(int flag, boolean value) throws RemoteException { Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value); synchronized (mLock) { checkNotClosedLocked(); - int halResult; - try { - halResult = mHwSession.setConfigFlag(flag, value); - } catch (RemoteException ex) { - throw new RuntimeException("Failed to set flag " + ConfigFlag.toString(flag), ex); - } + int halResult = mHwSession.setConfigFlag(flag, value); Convert.throwOnError("setConfigFlag", halResult); } } diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java index 40a93c1c2808..51499f735bcf 100644 --- a/services/core/java/com/android/server/content/SyncJobService.java +++ b/services/core/java/com/android/server/content/SyncJobService.java @@ -122,10 +122,12 @@ public class SyncJobService extends JobService { final long startUptime = mJobStartUptimes.get(jobId); final long nowUptime = SystemClock.uptimeMillis(); + final long runtime = nowUptime - startUptime; + if (startUptime == 0) { wtf("Job " + jobId + " start uptime not found: " + " params=" + jobParametersToString(params)); - } else if ((nowUptime - startUptime) > 60 * 1000) { + } else if (runtime > 60 * 1000) { // WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon. // (1 minute threshold.) if (!mStartedSyncs.get(jobId)) { @@ -134,6 +136,12 @@ public class SyncJobService extends JobService { + " nowUptime=" + nowUptime + " params=" + jobParametersToString(params)); } + } else if (runtime < 10 * 1000) { + // Job stopped too soon. WTF. + wtf("Job " + jobId + " stopped in " + runtime + " ms: " + + " startUptime=" + startUptime + + " nowUptime=" + nowUptime + + " params=" + jobParametersToString(params)); } mStartedSyncs.delete(jobId); @@ -183,6 +191,7 @@ public class SyncJobService extends JobService { return "job:null"; } else { return "job:#" + params.getJobId() + ":" + + "sr=[" + params.getStopReason() + "/" + params.getDebugStopReason() + "]:" + SyncOperation.maybeCreateFromJobExtras(params.getExtras()); } } diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index cbb1c0139bca..1e94e00a09a9 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -149,7 +149,7 @@ public class BrightnessTracker { /** * Start listening for brightness slider events * - * @param brightness the initial screen brightness + * @param initialBrightness the initial screen brightness */ public void start(float initialBrightness) { if (DEBUG) { @@ -219,8 +219,8 @@ public class BrightnessTracker { if (includePackage) { out.add(events[i]); } else { - BrightnessChangeEvent event = new BrightnessChangeEvent((events[i])); - event.packageName = null; + BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]), + /* redactPackage */ true); out.add(event); } } @@ -246,7 +246,8 @@ public class BrightnessTracker { } private void handleBrightnessChanged(float brightness, boolean userInitiated) { - final BrightnessChangeEvent event; + BrightnessChangeEvent.Builder builder; + synchronized (mDataCollectionLock) { if (!mStarted) { // Not currently gathering brightness change information @@ -263,9 +264,9 @@ public class BrightnessTracker { return; } - - event = new BrightnessChangeEvent(); - event.timeStamp = mInjector.currentTimeMillis(); + builder = new BrightnessChangeEvent.Builder(); + builder.setBrightness(brightness); + builder.setTimeStamp(mInjector.currentTimeMillis()); final int readingCount = mLastSensorReadings.size(); if (readingCount == 0) { @@ -273,8 +274,8 @@ public class BrightnessTracker { return; } - event.luxValues = new float[readingCount]; - event.luxTimestamps = new long[readingCount]; + float[] luxValues = new float[readingCount]; + long[] luxTimestamps = new long[readingCount]; int pos = 0; @@ -282,33 +283,35 @@ public class BrightnessTracker { long currentTimeMillis = mInjector.currentTimeMillis(); long elapsedTimeNanos = mInjector.elapsedRealtimeNanos(); for (LightData reading : mLastSensorReadings) { - event.luxValues[pos] = reading.lux; - event.luxTimestamps[pos] = currentTimeMillis - + luxValues[pos] = reading.lux; + luxTimestamps[pos] = currentTimeMillis - TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp); ++pos; } + builder.setLuxValues(luxValues); + builder.setLuxTimestamps(luxTimestamps); - event.batteryLevel = mLastBatteryLevel; - event.lastBrightness = previousBrightness; + builder.setBatteryLevel(mLastBatteryLevel); + builder.setLastBrightness(previousBrightness); } - event.brightness = brightness; - try { final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack(); - event.userId = focusedStack.userId; - event.packageName = focusedStack.topActivity.getPackageName(); + builder.setUserId(focusedStack.userId); + builder.setPackageName(focusedStack.topActivity.getPackageName()); } catch (RemoteException e) { // Really shouldn't be possible. + return; } - event.nightMode = mInjector.getSecureIntForUser(mContentResolver, + builder.setNightMode(mInjector.getSecureIntForUser(mContentResolver, Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 0, UserHandle.USER_CURRENT) - == 1; - event.colorTemperature = mInjector.getSecureIntForUser(mContentResolver, + == 1); + builder.setColorTemperature(mInjector.getSecureIntForUser(mContentResolver, Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, - 0, UserHandle.USER_CURRENT); + 0, UserHandle.USER_CURRENT)); + BrightnessChangeEvent event = builder.build(); if (DEBUG) { Slog.d(TAG, "Event " + event.brightness + " " + event.packageName); } @@ -457,40 +460,43 @@ public class BrightnessTracker { } tag = parser.getName(); if (TAG_EVENT.equals(tag)) { - BrightnessChangeEvent event = new BrightnessChangeEvent(); + BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder(); String brightness = parser.getAttributeValue(null, ATTR_NITS); - event.brightness = Float.parseFloat(brightness); + builder.setBrightness(Float.parseFloat(brightness)); String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP); - event.timeStamp = Long.parseLong(timestamp); - event.packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); + builder.setTimeStamp(Long.parseLong(timestamp)); + builder.setPackageName(parser.getAttributeValue(null, ATTR_PACKAGE_NAME)); String user = parser.getAttributeValue(null, ATTR_USER); - event.userId = mInjector.getUserId(mUserManager, Integer.parseInt(user)); + builder.setUserId(mInjector.getUserId(mUserManager, Integer.parseInt(user))); String batteryLevel = parser.getAttributeValue(null, ATTR_BATTERY_LEVEL); - event.batteryLevel = Float.parseFloat(batteryLevel); + builder.setBatteryLevel(Float.parseFloat(batteryLevel)); String nightMode = parser.getAttributeValue(null, ATTR_NIGHT_MODE); - event.nightMode = Boolean.parseBoolean(nightMode); + builder.setNightMode(Boolean.parseBoolean(nightMode)); String colorTemperature = parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE); - event.colorTemperature = Integer.parseInt(colorTemperature); + builder.setColorTemperature(Integer.parseInt(colorTemperature)); String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_NITS); - event.lastBrightness = Float.parseFloat(lastBrightness); + builder.setLastBrightness(Float.parseFloat(lastBrightness)); String luxValue = parser.getAttributeValue(null, ATTR_LUX); String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS); - String[] luxValues = luxValue.split(","); - String[] luxTimestamps = luxTimestamp.split(","); - if (luxValues.length != luxTimestamps.length) { + String[] luxValuesStrings = luxValue.split(","); + String[] luxTimestampsStrings = luxTimestamp.split(","); + if (luxValuesStrings.length != luxTimestampsStrings.length) { continue; } - event.luxValues = new float[luxValues.length]; - event.luxTimestamps = new long[luxValues.length]; + float[] luxValues = new float[luxValuesStrings.length]; + long[] luxTimestamps = new long[luxValuesStrings.length]; for (int i = 0; i < luxValues.length; ++i) { - event.luxValues[i] = Float.parseFloat(luxValues[i]); - event.luxTimestamps[i] = Long.parseLong(luxTimestamps[i]); + luxValues[i] = Float.parseFloat(luxValuesStrings[i]); + luxTimestamps[i] = Long.parseLong(luxTimestampsStrings[i]); } + builder.setLuxValues(luxValues); + builder.setLuxTimestamps(luxTimestamps); + BrightnessChangeEvent event = builder.build(); if (DEBUG) { Slog.i(TAG, "Read event " + event.brightness + " " + event.packageName); diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java index 85686ae90250..4f53ed49002b 100644 --- a/services/core/java/com/android/server/display/ColorFade.java +++ b/services/core/java/com/android/server/display/ColorFade.java @@ -99,7 +99,7 @@ final class ColorFade { private final float mProjMatrix[] = new float[16]; private final int[] mGLBuffers = new int[2]; private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc; - private int mOpacityLoc, mGammaLoc, mSaturationLoc; + private int mOpacityLoc, mGammaLoc; private int mProgram; // Vertex and corresponding texture coordinates. @@ -245,7 +245,6 @@ final class ColorFade { mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity"); mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma"); - mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation"); mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit"); GLES20.glUseProgram(mProgram); @@ -393,9 +392,8 @@ final class ColorFade { double cos = Math.cos(Math.PI * one_minus_level); double sign = cos < 0 ? -1 : 1; float opacity = (float) -Math.pow(one_minus_level, 2) + 1; - float saturation = (float) Math.pow(level, 4); float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d); - drawFaded(opacity, 1.f / gamma, saturation); + drawFaded(opacity, 1.f / gamma); if (checkGlErrors("drawFrame")) { return false; } @@ -407,10 +405,9 @@ final class ColorFade { return showSurface(1.0f); } - private void drawFaded(float opacity, float gamma, float saturation) { + private void drawFaded(float opacity, float gamma) { if (DEBUG) { - Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma + - ", saturation=" + saturation); + Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma); } // Use shaders GLES20.glUseProgram(mProgram); @@ -420,7 +417,6 @@ final class ColorFade { GLES20.glUniformMatrix4fv(mTexMatrixLoc, 1, false, mTexMatrix, 0); GLES20.glUniform1f(mOpacityLoc, opacity); GLES20.glUniform1f(mGammaLoc, gamma); - GLES20.glUniform1f(mSaturationLoc, saturation); // Use textures GLES20.glActiveTexture(GLES20.GL_TEXTURE0); diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 733ed3d81555..bd1dbf9c46e8 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -934,12 +934,14 @@ public final class JobSchedulerService extends com.android.server.SystemService * @param uid Uid of the calling client. * @param jobId Id of the job, provided at schedule-time. */ - public boolean cancelJob(int uid, int jobId) { + public boolean cancelJob(int uid, int jobId, int callingUid) { JobStatus toCancel; synchronized (mLock) { toCancel = mJobs.getJobByUidAndJobId(uid, jobId); if (toCancel != null) { - cancelJobImplLocked(toCancel, null, "cancel() called by app"); + cancelJobImplLocked(toCancel, null, + "cancel() called by app, callingUid=" + callingUid + + " uid=" + uid + " jobId=" + jobId); } return (toCancel != null); } @@ -2341,7 +2343,8 @@ public final class JobSchedulerService extends com.android.server.SystemService final int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { - JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app"); + JobSchedulerService.this.cancelJobsForUid(uid, + "cancelAll() called by app, callingUid=" + uid); } finally { Binder.restoreCallingIdentity(ident); } @@ -2353,7 +2356,7 @@ public final class JobSchedulerService extends com.android.server.SystemService long ident = Binder.clearCallingIdentity(); try { - JobSchedulerService.this.cancelJob(uid, jobId); + JobSchedulerService.this.cancelJob(uid, jobId, uid); } finally { Binder.restoreCallingIdentity(ident); } @@ -2466,7 +2469,7 @@ public final class JobSchedulerService extends com.android.server.SystemService for (int i=0; i<mActiveServices.size(); i++) { final JobServiceContext jc = mActiveServices.get(i); final JobStatus js = jc.getRunningJobLocked(); - if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId)) { + if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) { foundSome = true; pw.print("Timing out: "); js.printUniqueId(pw); @@ -2506,7 +2509,7 @@ public final class JobSchedulerService extends com.android.server.SystemService } } else { pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId); - if (!cancelJob(pkgUid, jobId)) { + if (!cancelJob(pkgUid, jobId, Process.SHELL_UID)) { pw.println("No matching job found."); } } diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 709deeb81ac9..83a3c1993d4b 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -312,13 +312,14 @@ public final class JobServiceContext implements ServiceConnection { return mTimeoutElapsed; } - boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId) { + boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId, + String reason) { final JobStatus executing = getRunningJobLocked(); if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId()) && (pkgName == null || pkgName.equals(executing.getSourcePackageName())) && (!matchJobId || jobId == executing.getJobId())) { if (mVerb == VERB_EXECUTING) { - mParams.setStopReason(JobParameters.REASON_TIMEOUT); + mParams.setStopReason(JobParameters.REASON_TIMEOUT, reason); sendStopMessageLocked("force timeout from shell"); return true; } @@ -537,7 +538,7 @@ public final class JobServiceContext implements ServiceConnection { } return; } - mParams.setStopReason(arg1); + mParams.setStopReason(arg1, debugReason); if (arg1 == JobParameters.REASON_PREEMPT) { mPreferredUid = mRunningJob != null ? mRunningJob.getUid() : NO_PREFERRED_UID; @@ -687,7 +688,7 @@ public final class JobServiceContext implements ServiceConnection { // Not an error - client ran out of time. Slog.i(TAG, "Client timed out while executing (no jobFinished received), " + "sending onStop: " + getRunningJobNameLocked()); - mParams.setStopReason(JobParameters.REASON_TIMEOUT); + mParams.setStopReason(JobParameters.REASON_TIMEOUT, "client timed out"); sendStopMessageLocked("timeout while executing"); break; default: diff --git a/services/core/java/com/android/server/media/MediaUpdateService.java b/services/core/java/com/android/server/media/MediaUpdateService.java new file mode 100644 index 000000000000..016d062db726 --- /dev/null +++ b/services/core/java/com/android/server/media/MediaUpdateService.java @@ -0,0 +1,151 @@ +/* + * Copyright 2018 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.media; + +import android.content.Context; +import android.content.Intent; +import android.media.IMediaResourceMonitor; +import android.os.Binder; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; +import android.util.Slog; +import com.android.server.SystemService; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.PatternMatcher; +import android.os.ServiceManager; +import android.media.IMediaExtractorUpdateService; + +import java.lang.Exception; + +/** This class provides a system service that manages media framework updates. */ +public class MediaUpdateService extends SystemService { + private static final String TAG = "MediaUpdateService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final String MEDIA_UPDATE_PACKAGE_NAME = "com.android.media.update"; + private static final String EXTRACTOR_UPDATE_SERVICE_NAME = "media.extractor.update"; + + private IMediaExtractorUpdateService mMediaExtractorUpdateService; + + public MediaUpdateService(Context context) { + super(context); + } + + @Override + public void onStart() { + // TODO: Uncomment below once sepolicy change is landed. + /* + if ("userdebug".equals(android.os.Build.TYPE) || "eng".equals(android.os.Build.TYPE)) { + connect(); + registerBroadcastReceiver(); + } + */ + } + + private void connect() { + IBinder binder = ServiceManager.getService(EXTRACTOR_UPDATE_SERVICE_NAME); + if (binder != null) { + try { + binder.linkToDeath(new IBinder.DeathRecipient() { + @Override + public void binderDied() { + Slog.w(TAG, "mediaextractor died; reconnecting"); + mMediaExtractorUpdateService = null; + connect(); + } + }, 0); + } catch (Exception e) { + binder = null; + } + } + if (binder != null) { + mMediaExtractorUpdateService = IMediaExtractorUpdateService.Stub.asInterface(binder); + packageStateChanged(); + } else { + Slog.w(TAG, EXTRACTOR_UPDATE_SERVICE_NAME + " not found."); + } + } + + private void registerBroadcastReceiver() { + BroadcastReceiver updateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM) + != UserHandle.USER_SYSTEM) { + // Ignore broadcast for non system users. We don't want to update system + // service multiple times. + return; + } + switch (intent.getAction()) { + case Intent.ACTION_PACKAGE_REMOVED: + if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) { + // The existing package is updated. Will be handled with the + // following ACTION_PACKAGE_ADDED case. + return; + } + packageStateChanged(); + break; + case Intent.ACTION_PACKAGE_CHANGED: + packageStateChanged(); + break; + case Intent.ACTION_PACKAGE_ADDED: + packageStateChanged(); + break; + } + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + filter.addDataSchemeSpecificPart(MEDIA_UPDATE_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL); + + getContext().registerReceiverAsUser(updateReceiver, UserHandle.ALL, filter, + null /* broadcast permission */, null /* handler */); + } + + private void packageStateChanged() { + ApplicationInfo packageInfo = null; + boolean pluginsAvailable = false; + try { + packageInfo = getContext().getPackageManager().getApplicationInfo( + MEDIA_UPDATE_PACKAGE_NAME, PackageManager.MATCH_SYSTEM_ONLY); + pluginsAvailable = packageInfo.enabled; + } catch (Exception e) { + Slog.v(TAG, "package '" + MEDIA_UPDATE_PACKAGE_NAME + "' not installed"); + } + loadExtractorPlugins( + (packageInfo != null && pluginsAvailable) ? packageInfo.sourceDir : ""); + } + + private void loadExtractorPlugins(String apkPath) { + try { + if (mMediaExtractorUpdateService != null) { + mMediaExtractorUpdateService.loadPlugins(apkPath); + } + } catch (Exception e) { + Slog.w(TAG, "Error in loadPlugins", e); + } + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 575c44d15209..16b92573f222 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2917,12 +2917,34 @@ public class NotificationManagerService extends SystemService { } } + /** + * Sets the notification policy. Apps that target API levels below + * {@link android.os.Build.VERSION_CODES#P} cannot make DND silence + * {@link Policy#PRIORITY_CATEGORY_ALARMS} or + * {@link Policy#PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER} + */ @Override public void setNotificationPolicy(String pkg, Policy policy) { enforcePolicyAccess(pkg, "setNotificationPolicy"); final long identity = Binder.clearCallingIdentity(); try { + final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg, + 0, UserHandle.getUserId(MY_UID)); + + if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1) { + Policy currPolicy = mZenModeHelper.getNotificationPolicy(); + + int priorityCategories = policy.priorityCategories + | (currPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) + | (currPolicy.priorityCategories & + Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER); + policy = new Policy(priorityCategories, + policy.priorityCallSenders, policy.priorityMessageSenders, + policy.suppressedVisualEffects); + } + mZenModeHelper.setNotificationPolicy(policy); + } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index b06b58385cc3..1717b3d1a5f7 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -652,6 +652,7 @@ public class LauncherAppsService extends SystemService { activityInfo.name.equals(component.getClassName())) { // Found an activity with category launcher that matches // this component so ok to launch. + launchIntent.setPackage(null); launchIntent.setComponent(component); mContext.startActivityAsUser(launchIntent, opts, user); return; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1e2f2b2df863..6e9452388c09 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2697,6 +2697,12 @@ public class PackageManagerService extends IPackageManager.Stub mSettings.getDisabledSystemPkgLPr(ps.name); if (disabledPs.codePath == null || !disabledPs.codePath.exists() || disabledPs.pkg == null) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Possibly deleted app: " + ps.dumpState_temp() + + "; path: " + (disabledPs.codePath == null ? "<<NULL>>":disabledPs.codePath) + + "; pkg: " + (disabledPs.pkg==null?"<<NULL>>":disabledPs.pkg.toString())); +} possiblyDeletedUpdatedSystemApps.add(ps.name); } } @@ -2748,6 +2754,10 @@ public class PackageManagerService extends IPackageManager.Stub for (String deletedAppName : possiblyDeletedUpdatedSystemApps) { PackageParser.Package deletedPkg = mPackages.get(deletedAppName); mSettings.removeDisabledSystemPackageLPw(deletedAppName); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "remove update; name: " + deletedAppName + ", exists? " + (deletedPkg != null)); +} final String msg; if (deletedPkg == null) { // should have found an update, but, we didn't; remove everything @@ -8311,6 +8321,8 @@ public class PackageManagerService extends IPackageManager.Stub return scannedPkg; } + // Temporary to catch potential issues with refactoring + private static boolean REFACTOR_DEBUG = true; /** * Adds a new package to the internal data structures during platform initialization. * <p>After adding, the package is known to the system and available for querying. @@ -8351,6 +8363,10 @@ public class PackageManagerService extends IPackageManager.Stub synchronized (mPackages) { renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage); final String realPkgName = getRealPackageName(pkg, renamedPkgName); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Add pkg: " + pkg.packageName + (realPkgName==null?"":", realName: " + realPkgName)); +} if (realPkgName != null) { ensurePackageRenamed(pkg, renamedPkgName); } @@ -8365,6 +8381,12 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_INSTALL && isSystemPkgUpdated) { Slog.d(TAG, "updatedPkg = " + disabledPkgSetting); } +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "SSP? " + scanSystemPartition + + ", exists? " + pkgAlreadyExists + (pkgAlreadyExists?" "+pkgSetting.toString():"") + + ", upgraded? " + isSystemPkgUpdated + (isSystemPkgUpdated?" "+disabledPkgSetting.toString():"")); +} final SharedUserSetting sharedUserSetting = (pkg.mSharedUserId != null) ? mSettings.getSharedUserLPw(pkg.mSharedUserId, @@ -8376,6 +8398,12 @@ public class PackageManagerService extends IPackageManager.Stub Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + sharedUserSetting.userId + "):" + " packages=" + sharedUserSetting.packages); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Shared UserID " + pkg.mSharedUserId + + " (uid=" + sharedUserSetting.userId + "):" + + " packages=" + sharedUserSetting.packages); +} } if (scanSystemPartition) { @@ -8384,6 +8412,10 @@ public class PackageManagerService extends IPackageManager.Stub // version on /data, cycle through all of its children packages and // remove children that are no longer defined. if (isSystemPkgUpdated) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Disable child packages"); +} final int scannedChildCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; final int disabledChildCount = disabledPkgSetting.childPackageNames != null @@ -8395,11 +8427,19 @@ public class PackageManagerService extends IPackageManager.Stub for (int j = 0; j < scannedChildCount; j++) { PackageParser.Package childPkg = pkg.childPackages.get(j); if (childPkg.packageName.equals(disabledChildPackageName)) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Ignore " + disabledChildPackageName); +} disabledPackageAvailable = true; break; } } if (!disabledPackageAvailable) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Disable " + disabledChildPackageName); +} mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName); } } @@ -8408,17 +8448,44 @@ public class PackageManagerService extends IPackageManager.Stub disabledPkgSetting /* pkgSetting */, null /* disabledPkgSetting */, null /* originalPkgSetting */, null, parseFlags, scanFlags, (pkg == mPlatformPackage), user); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Scan disabled system package"); +Slog.e("TODD", + "Pre: " + request.pkgSetting.dumpState_temp()); +} +final ScanResult result = scanPackageOnlyLI(request, mFactoryTest, -1L); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Post: " + (result.success?result.pkgSetting.dumpState_temp():"FAILED scan")); +} } } } final boolean newPkgChangedPaths = pkgAlreadyExists && !pkgSetting.codePathString.equals(pkg.codePath); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "paths changed? " + newPkgChangedPaths + + "; old: " + pkg.codePath + + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.codePathString)); +} final boolean newPkgVersionGreater = pkgAlreadyExists && pkg.getLongVersionCode() > pkgSetting.versionCode; +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "version greater? " + newPkgVersionGreater + + "; old: " + pkg.getLongVersionCode() + + ", new: " + (pkgSetting==null?"<<NULL>>":pkgSetting.versionCode)); +} final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated && newPkgChangedPaths && newPkgVersionGreater; +if (REFACTOR_DEBUG) { + Slog.e("TODD", + "system better? " + isSystemPkgBetter); +} if (isSystemPkgBetter) { // The version of the application on /system is greater than the version on // /data. Switch back to the application on /system. @@ -8434,6 +8501,13 @@ public class PackageManagerService extends IPackageManager.Stub + " name: " + pkgSetting.name + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode() + "; " + pkgSetting.codePathString + " --> " + pkg.codePath); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "System package changed;" + + " name: " + pkgSetting.name + + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode() + + "; " + pkgSetting.codePathString + " --> " + pkg.codePath); +} final InstallArgs args = createInstallArgsForExisting( packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString, @@ -8445,6 +8519,10 @@ public class PackageManagerService extends IPackageManager.Stub } if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "THROW exception; system pkg version not good enough"); +} // The version of the application on the /system partition is less than or // equal to the version on the /data partition. Throw an exception and use // the application already installed on the /data partition. @@ -8470,6 +8548,11 @@ public class PackageManagerService extends IPackageManager.Stub logCriticalInfo(Log.WARN, "System package signature mismatch;" + " name: " + pkgSetting.name); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "System package signature mismatch;" + + " name: " + pkgSetting.name); +} try (PackageFreezer freezer = freezePackage(pkg.packageName, "scanPackageInternalLI")) { deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null); @@ -8484,6 +8567,13 @@ public class PackageManagerService extends IPackageManager.Stub + " name: " + pkgSetting.name + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode() + "; " + pkgSetting.codePathString + " --> " + pkg.codePath); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "System package enabled;" + + " name: " + pkgSetting.name + + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode() + + "; " + pkgSetting.codePathString + " --> " + pkg.codePath); +} InstallArgs args = createInstallArgsForExisting( packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString, pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting)); @@ -8500,13 +8590,35 @@ public class PackageManagerService extends IPackageManager.Stub + " name: " + pkgSetting.name + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode + "; new: " + pkg.codePath + " @ " + pkg.codePath); +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "System package disabled;" + + " name: " + pkgSetting.name + + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode + + "; new: " + pkg.codePath + " @ " + pkg.codePath); +} } } +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Scan package"); +Slog.e("TODD", + "Pre: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp())); +} final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user); +if (REFACTOR_DEBUG) { +pkgSetting = mSettings.getPackageLPr(pkg.packageName); +Slog.e("TODD", + "Post: " + (pkgSetting==null?"<<NONE>>":pkgSetting.dumpState_temp())); +} if (shouldHideSystemApp) { +if (REFACTOR_DEBUG) { +Slog.e("TODD", + "Disable package: " + pkg.packageName); +} synchronized (mPackages) { mSettings.disableSystemPackageLPw(pkg.packageName, true); } diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 2b91b7d38b4f..2a2430c07980 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -97,6 +97,35 @@ public final class PackageSetting extends PackageSettingBase { + " " + name + "/" + appId + "}"; } + // Temporary to catch potential issues with refactoring + public String dumpState_temp() { + String flags = ""; + flags += ((pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0 ? "U" : ""); + flags += ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 ? "S" : ""); + if ("".equals(flags)) { + flags = "-"; + } + String privFlags = ""; + privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0 ? "P" : ""); + privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0 ? "O" : ""); + privFlags += ((pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0 ? "V" : ""); + if ("".equals(privFlags)) { + privFlags = "-"; + } + return "PackageSetting{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + name + (realName == null ? "" : "("+realName+")") + "/" + appId + (sharedUser==null?"":" u:" + sharedUser.name+"("+sharedUserId+")") + + ", ver:" + versionCode + + ", path: " + codePath + + ", pABI: " + primaryCpuAbiString + + ", sABI: " + secondaryCpuAbiString + + ", oABI: " + cpuAbiOverrideString + + ", flags: " + flags + + ", privFlags: " + privFlags + + ", pkg: " + (pkg == null ? "<<NULL>>" : pkg.dumpState_temp()) + + "}"; + } + public void copyFrom(PackageSetting orig) { super.copyFrom(orig); doCopy(orig); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 50690cb0f33e..cc07d82554e9 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -25,12 +25,14 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; +import android.provider.Settings; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.util.Log; @@ -119,7 +121,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_AIRPLANE_MODE, UserManager.DISALLOW_CONFIG_BRIGHTNESS, UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, - UserManager.DISALLOW_AMBIENT_DISPLAY + UserManager.DISALLOW_AMBIENT_DISPLAY, + UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT }); /** @@ -542,6 +545,22 @@ public class UserRestrictionsUtils { android.provider.Settings.Global.SAFE_BOOT_DISALLOWED, newValue ? 1 : 0); break; + case UserManager.DISALLOW_AIRPLANE_MODE: + if (newValue) { + final boolean airplaneMode = Settings.Global.getInt( + context.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + if (airplaneMode) { + android.provider.Settings.Global.putInt( + context.getContentResolver(), + android.provider.Settings.Global.AIRPLANE_MODE_ON, 0); + // Post the intent. + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", 0); + context.sendBroadcastAsUser(intent, UserHandle.ALL); + } + } + break; } } finally { Binder.restoreCallingIdentity(id); diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java index d6281c51b3fa..a517d6d1a99e 100644 --- a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java @@ -111,6 +111,7 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { final long ident = mInjector.clearCallingIdentity(); try { + launchIntent.setPackage(null); launchIntent.setComponent(component); mContext.startActivityAsUser(launchIntent, ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(), user); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 79f20c57a032..a453c333d560 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -48,7 +48,6 @@ import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN; @@ -65,6 +64,9 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; @@ -161,6 +163,7 @@ import android.app.StatusBarManager; import android.app.UiModeManager; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; +import android.content.ComponentCallbacks; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -266,6 +269,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; +import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.policy.PhoneWindow; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ArrayUtils; @@ -2741,6 +2745,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override + public void onOverlayChangedLw() { + onConfigurationChanged(); + } + + @Override public void onConfigurationChanged() { // TODO(multi-display): Define policy for secondary displays. Context uiContext = getSystemUiContext(); @@ -4203,20 +4212,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (isKeyguardShowingAndNotOccluded()) { // don't launch home if keyguard showing return; - } - - if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) { + } else if (mKeyguardOccluded && mKeyguardDelegate.isShowing()) { + mKeyguardDelegate.dismiss(new KeyguardDismissCallback() { + @Override + public void onDismissSucceeded() throws RemoteException { + mHandler.post(() -> { + startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); + }); + } + }, null /* message */); + return; + } else if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) { // when in keyguard restricted mode, must first verify unlock // before launching home mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() { @Override public void onKeyguardExitResult(boolean success) { if (success) { - try { - ActivityManager.getService().stopAppSwitches(); - } catch (RemoteException e) { - } - sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); } } @@ -4226,11 +4238,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // no keyguard stuff to worry about, just launch home! - try { - ActivityManager.getService().stopAppSwitches(); - } catch (RemoteException e) { - } if (mRecentsVisible) { + try { + ActivityManager.getService().stopAppSwitches(); + } catch (RemoteException e) {} + // Hide Recents and notify it to launch Home if (awakenFromDreams) { awakenDreams(); @@ -4238,7 +4250,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { hideRecentApps(false, true); } else { // Otherwise, just launch Home - sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); } } @@ -4861,7 +4872,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int type = attrs.type; final int fl = PolicyControl.getWindowFlags(win, attrs); - final long fl2 = attrs.flags2; final int pfl = attrs.privateFlags; final int sim = attrs.softInputMode; final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null); @@ -4883,12 +4893,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int adjust = sim & SOFT_INPUT_MASK_ADJUST; final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0 - || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0 - || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0; + || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN; final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR; - final boolean layoutInCutout = (fl2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; sf.set(displayFrames.mStable); @@ -5048,9 +5056,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { // moving from a window that is not hiding the status bar to one that is. cf.set(displayFrames.mRestricted); } - if (requestedFullscreen && !layoutInCutout) { - pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); - } applyStableConstraints(sysUiFl, fl, cf, displayFrames); if (adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); @@ -5136,9 +5141,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { of.set(displayFrames.mUnrestricted); df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); - if (requestedFullscreen && !layoutInCutout) { - pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); - } } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) { of.set(displayFrames.mRestricted); df.set(displayFrames.mRestricted); @@ -5213,15 +5215,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - // Ensure that windows that did not request to be laid out in the cutout don't get laid - // out there. - if (!layoutInCutout) { + final int cutoutMode = attrs.layoutInDisplayCutoutMode; + // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in + // the cutout safe zone. + if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { final Rect displayCutoutSafeExceptMaybeTop = mTmpRect; displayCutoutSafeExceptMaybeTop.set(displayFrames.mDisplayCutoutSafe); - if (layoutInScreen && layoutInsetDecor) { + if (layoutInScreen && layoutInsetDecor && !requestedFullscreen + && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) { // At the top we have the status bar, so apps that are - // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR already expect that there's an inset - // there and we don't need to exclude the window from that area. + // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN + // already expect that there's an inset there and we don't need to exclude + // the window from that area. displayCutoutSafeExceptMaybeTop.top = Integer.MIN_VALUE; } pf.intersectUnchecked(displayCutoutSafeExceptMaybeTop); @@ -7631,6 +7636,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) { + try { + ActivityManager.getService().stopAppSwitches(); + } catch (RemoteException e) {} + sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY); + if (awakenFromDreams) { awakenDreams(); } @@ -7670,11 +7680,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if (false) { // This code always brings home to the front. - try { - ActivityManager.getService().stopAppSwitches(); - } catch (RemoteException e) { - } - sendCloseSystemWindows(); startDockOrHome(false /*fromHomeKey*/, true /* awakenFromDreams */); } else { // This code brings home to the front or, if it is already @@ -7898,8 +7903,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { // If the top fullscreen-or-dimming window is also the top fullscreen, respect // its light flag. vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null) - & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + if (!statusColorWin.isLetterboxedForDisplayCutoutLw()) { + // Only allow white status bar if the window was not letterboxed. + vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null) + & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } } else if (statusColorWin != null && statusColorWin.isDimming()) { // Otherwise if it's dimming, clear the light flag. vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 686155b40985..60dae534547e 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -138,11 +138,6 @@ import java.lang.annotation.RetentionPolicy; * </dl> */ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { - // Navigation bar position values - int NAV_BAR_LEFT = 1 << 0; - int NAV_BAR_RIGHT = 1 << 1; - int NAV_BAR_BOTTOM = 1 << 2; - @Retention(SOURCE) @IntDef({NAV_BAR_LEFT, NAV_BAR_RIGHT, NAV_BAR_BOTTOM}) @interface NavigationBarPosition {} @@ -176,6 +171,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { void onKeyguardOccludedChangedLw(boolean occluded); /** + * Called when the resource overlays change. + */ + default void onOverlayChangedLw() {} + + /** * Interface to the Window Manager state associated with a particular * window. You can hold on to an instance of this interface from the call * to prepareAddWindow() until removeWindow(). @@ -446,6 +446,13 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { */ public boolean isDimming(); + /** + * Returns true if the window is letterboxed for the display cutout. + */ + default boolean isLetterboxedForDisplayCutoutLw() { + return false; + } + /** @return the current windowing mode of this window. */ int getWindowingMode(); diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java index 1b5a5216909d..48a196dfcff1 100644 --- a/services/core/java/com/android/server/policy/WindowOrientationListener.java +++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java @@ -32,6 +32,7 @@ import android.util.proto.ProtoOutputStream; import android.view.Surface; import java.io.PrintWriter; +import java.util.List; /** * A special helper class used by the WindowManager @@ -90,7 +91,28 @@ public abstract class WindowOrientationListener { mHandler = handler; mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); mRate = rate; - mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_DEVICE_ORIENTATION); + List<Sensor> l = mSensorManager.getSensorList(Sensor.TYPE_DEVICE_ORIENTATION); + Sensor wakeUpDeviceOrientationSensor = null; + Sensor nonWakeUpDeviceOrientationSensor = null; + /** + * Prefer the wakeup form of the sensor if implemented. + * It's OK to look for just two types of this sensor and use + * the last found. Typical devices will only have one sensor of + * this type. + */ + for (Sensor s : l) { + if (s.isWakeUpSensor()) { + wakeUpDeviceOrientationSensor = s; + } else { + nonWakeUpDeviceOrientationSensor = s; + } + } + + if (wakeUpDeviceOrientationSensor != null) { + mSensor = wakeUpDeviceOrientationSensor; + } else { + mSensor = nonWakeUpDeviceOrientationSensor; + } if (mSensor != null) { mOrientationJudge = new OrientationSensorJudge(); diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 2c84cb1a4d6f..a254ba2cab31 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -1427,13 +1427,13 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mLetterbox.setDimensions(mPendingTransaction, getParent().getBounds(), w.mFrame); } else if (mLetterbox != null) { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + // Make sure we have a transaction here, in case we're called outside of a transaction. + // This does not use mPendingTransaction, because SurfaceAnimator uses a + // global transaction in onAnimationEnd. SurfaceControl.openTransaction(); try { mLetterbox.hide(t); } finally { - // TODO: This should use pendingTransaction eventually, but right now things - // happening on the animation finished callback are happening on the global - // transaction. SurfaceControl.mergeToGlobalTransaction(t); SurfaceControl.closeTransaction(); } diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 28b4c1dbeb8e..0171b56ffc47 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -136,7 +136,7 @@ class DragDropController { outSurface.copyFrom(surface); final IBinder winBinder = window.asBinder(); IBinder token = new Binder(); - mDragState = new DragState(mService, token, surface, flags, winBinder); + mDragState = new DragState(mService, this, token, surface, flags, winBinder); mDragState.mPid = callerPid; mDragState.mUid = callerUid; mDragState.mOriginalAlpha = alpha; diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index b9f437af20e4..1ac9b88749a7 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -42,6 +42,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.os.IUserManager; +import android.os.UserManagerInternal; import android.util.Slog; import android.view.Display; import android.view.DragEvent; @@ -55,6 +56,7 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.android.internal.view.IDragAndDropPermissions; +import com.android.server.LocalServices; import com.android.server.input.InputApplicationHandle; import com.android.server.input.InputWindowHandle; @@ -116,10 +118,10 @@ class DragState { private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); private Point mDisplaySize = new Point(); - DragState(WindowManagerService service, IBinder token, SurfaceControl surface, - int flags, IBinder localWin) { + DragState(WindowManagerService service, DragDropController controller, IBinder token, + SurfaceControl surface, int flags, IBinder localWin) { mService = service; - mDragDropController = service.mDragDropController; + mDragDropController = controller; mToken = token; mSurfaceControl = surface; mFlags = flags; @@ -318,15 +320,9 @@ class DragState { mSourceUserId = UserHandle.getUserId(mUid); - final IUserManager userManager = - (IUserManager) ServiceManager.getService(Context.USER_SERVICE); - try { - mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean( - UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); - } catch (RemoteException e) { - Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e); - mCrossProfileCopyAllowed = false; - } + final UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class); + mCrossProfileCopyAllowed = !userManager.getUserRestriction( + mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); if (DEBUG_DRAG) { Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); @@ -534,7 +530,8 @@ class DragState { final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); final DragAndDropPermissionsHandler dragAndDropPermissions; - if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) { + if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0 + && mData != null) { dragAndDropPermissions = new DragAndDropPermissionsHandler( mData, mUid, @@ -546,7 +543,9 @@ class DragState { dragAndDropPermissions = null; } if (mSourceUserId != targetUserId){ - mData.fixUris(mSourceUserId); + if (mData != null) { + mData.fixUris(mSourceUserId); + } } final int myPid = Process.myPid(); final IBinder token = touchedWin.mClient.asBinder(); diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java index 69cbe4607cf1..62519e12d5d9 100644 --- a/services/core/java/com/android/server/wm/PinnedStackController.java +++ b/services/core/java/com/android/server/wm/PinnedStackController.java @@ -360,14 +360,19 @@ class PinnedStackController { * Sets the Ime state and height. */ void setAdjustedForIme(boolean adjustedForIme, int imeHeight) { - // Return early if there is no state change - if (mIsImeShowing == adjustedForIme && mImeHeight == imeHeight) { + // Due to the order of callbacks from the system, we may receive an ime height even when + // {@param adjustedForIme} is false, and also a zero height when {@param adjustedForIme} + // is true. Instead, ensure that the ime state changes with the height and if the ime is + // showing, then the height is non-zero. + final boolean imeShowing = adjustedForIme && imeHeight > 0; + imeHeight = imeShowing ? imeHeight : 0; + if (imeShowing == mIsImeShowing && imeHeight == mImeHeight) { return; } - mIsImeShowing = adjustedForIme; + mIsImeShowing = imeShowing; mImeHeight = imeHeight; - notifyImeVisibilityChanged(adjustedForIme, imeHeight); + notifyImeVisibilityChanged(imeShowing, imeHeight); notifyMovementBoundsChanged(true /* fromImeAdjustment */); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index e4fe88858184..d2ab9dfa68d1 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5894,6 +5894,7 @@ public class WindowManagerService extends IWindowManager.Stub * the screen is. * @see WindowManagerPolicy#getNavBarPosition() */ + @Override @WindowManagerPolicy.NavigationBarPosition public int getNavBarPosition() { synchronized (mWindowMap) { @@ -6596,6 +6597,13 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void onOverlayChanged() { + synchronized (mWindowMap) { + mPolicy.onOverlayChangedLw(); + requestTraversal(); + } + } + public void onDisplayChanged(int displayId) { synchronized (mWindowMap) { final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index dbad6ad0a9e0..d068c4957d47 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -21,14 +21,12 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.SurfaceControl.Transaction; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; -import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; @@ -43,6 +41,8 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FORMAT_CHANGED; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; @@ -202,7 +202,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Comparator; -import java.util.LinkedList; import java.util.function.Predicate; /** A window in the window manager. */ @@ -2981,25 +2980,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */ boolean isLetterboxedAppWindow() { - return !isInMultiWindowMode() && mAppToken != null && (!mAppToken.matchParentBounds() - || isLetterboxedForCutout()); + return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds() + || isLetterboxedForDisplayCutoutLw(); } - private boolean isLetterboxedForCutout() { + @Override + public boolean isLetterboxedForDisplayCutoutLw() { + if (mAppToken == null) { + // Only windows with an AppWindowToken are letterboxed. + return false; + } if (getDisplayContent().getDisplayInfo().displayCutout == null) { // No cutout, no letterbox. return false; } - if ((mAttrs.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0) { + if (mAttrs.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) { // Layout in cutout, no letterbox. return false; } // TODO: handle dialogs and other non-filling windows + if (mAttrs.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) { + // Never layout in cutout, always letterbox. + return true; + } // Letterbox if any fullscreen mode is set. final int fl = mAttrs.flags; final int sysui = mSystemUiVisibility; - return (fl & FLAG_FULLSCREEN) != 0 - || (sysui & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_FULLSCREEN)) != 0; + return (fl & FLAG_FULLSCREEN) != 0 || (sysui & (SYSTEM_UI_FLAG_FULLSCREEN)) != 0; } boolean isDragResizeChanged() { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d4ef3155afb0..62a97f8cdcfb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -226,6 +226,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -790,6 +791,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; private static final String TAG_IS_LOGOUT_ENABLED = "is_logout_enabled"; + private static final String TAG_MANDATORY_BACKUP_TRANSPORT = "mandatory_backup_transport"; DeviceAdminInfo info; @@ -906,6 +908,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // The blacklist data is stored in a file whose name is stored in the XML String passwordBlacklistFile = null; + // The component name of the backup transport which has to be used if backups are mandatory + // or null if backups are not mandatory. + ComponentName mandatoryBackupTransport = null; + ActiveAdmin(DeviceAdminInfo _info, boolean parent) { info = _info; isParent = parent; @@ -1169,6 +1175,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.attribute(null, ATTR_VALUE, Boolean.toString(isLogoutEnabled)); out.endTag(null, TAG_IS_LOGOUT_ENABLED); } + if (mandatoryBackupTransport != null) { + out.startTag(null, TAG_MANDATORY_BACKUP_TRANSPORT); + out.attribute(null, ATTR_VALUE, mandatoryBackupTransport.flattenToString()); + out.endTag(null, TAG_MANDATORY_BACKUP_TRANSPORT); + } } void writePackageListToXml(XmlSerializer out, String outerTag, @@ -1347,6 +1358,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else if (TAG_IS_LOGOUT_ENABLED.equals(tag)) { isLogoutEnabled = Boolean.parseBoolean( parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_MANDATORY_BACKUP_TRANSPORT.equals(tag)) { + mandatoryBackupTransport = ComponentName.unflattenFromString( + parser.getAttributeValue(null, ATTR_VALUE)); } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -11338,7 +11352,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } Preconditions.checkNotNull(admin); synchronized (this) { - getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + ActiveAdmin activeAdmin = getActiveAdminForCallerLocked( + admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + if (!enabled) { + activeAdmin.mandatoryBackupTransport = null; + saveSettingsLocked(UserHandle.USER_SYSTEM); + } } final long ident = mInjector.binderClearCallingIdentity(); @@ -11373,6 +11392,50 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public void setMandatoryBackupTransport( + ComponentName admin, ComponentName backupTransportComponent) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(admin); + synchronized (this) { + ActiveAdmin activeAdmin = + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + if (!Objects.equals(backupTransportComponent, activeAdmin.mandatoryBackupTransport)) { + activeAdmin.mandatoryBackupTransport = backupTransportComponent; + saveSettingsLocked(UserHandle.USER_SYSTEM); + } + } + final long identity = mInjector.binderClearCallingIdentity(); + try { + IBackupManager ibm = mInjector.getIBackupManager(); + if (ibm != null && backupTransportComponent != null) { + if (!ibm.isBackupServiceActive(UserHandle.USER_SYSTEM)) { + ibm.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + } + ibm.selectBackupTransportAsync(backupTransportComponent, null); + ibm.setBackupEnabled(true); + } + } catch (RemoteException e) { + throw new IllegalStateException("Failed to set mandatory backup transport.", e); + } finally { + mInjector.binderRestoreCallingIdentity(identity); + } + } + + @Override + public ComponentName getMandatoryBackupTransport() { + if (!mHasFeature) { + return null; + } + synchronized (this) { + ActiveAdmin activeAdmin = getDeviceOwnerAdminLocked(); + return activeAdmin == null ? null : activeAdmin.mandatoryBackupTransport; + } + } + + + @Override public boolean bindDeviceAdminServiceAsUser( @NonNull ComponentName admin, @NonNull IApplicationThread caller, @Nullable IBinder activtiyToken, @NonNull Intent serviceIntent, diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 4310a98d8000..3199bfa49455 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -80,6 +80,7 @@ import com.android.server.job.JobSchedulerService; import com.android.server.lights.LightsService; import com.android.server.media.MediaResourceMonitorService; import com.android.server.media.MediaRouterService; +import com.android.server.media.MediaUpdateService; import com.android.server.media.MediaSessionService; import com.android.server.media.projection.MediaProjectionManagerService; import com.android.server.net.NetworkPolicyManagerService; @@ -1442,6 +1443,10 @@ public final class SystemServer { mSystemServiceManager.startService(MediaSessionService.class); traceEnd(); + traceBeginAndSlog("StartMediaUpdateService"); + mSystemServiceManager.startService(MediaUpdateService.class); + traceEnd(); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) { traceBeginAndSlog("StartHdmiControlService"); mSystemServiceManager.startService(HdmiControlService.class); diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java index f9ebd28418cd..dc0a4e32b4a8 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -16,7 +16,11 @@ package com.android.server.backup; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES; +import static com.android.server.backup.testing.TransportData.backupTransport; +import static com.android.server.backup.testing.TransportData.d2dTransport; +import static com.android.server.backup.testing.TransportData.localTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransports; import static com.google.common.truth.Truth.assertThat; @@ -24,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -39,8 +44,10 @@ import android.platform.test.annotations.Presubmit; import android.provider.Settings; import com.android.server.backup.testing.ShadowAppBackupUtils; +import com.android.server.backup.testing.ShadowBackupPolicyEnforcer; +import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils; -import com.android.server.backup.testing.TransportTestUtils.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; @@ -57,38 +64,40 @@ import org.robolectric.shadows.ShadowContextWrapper; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowSettings; +import org.robolectric.shadows.ShadowSystemClock; import java.io.File; import java.util.HashMap; -import java.util.List; import java.util.Map; @RunWith(FrameworkRobolectricTestRunner.class) @Config( manifest = Config.NONE, sdk = 26, - shadows = {ShadowAppBackupUtils.class} + shadows = {ShadowAppBackupUtils.class, ShadowBackupPolicyEnforcer.class} ) @SystemLoaderClasses({RefactoredBackupManagerService.class, TransportManager.class}) @Presubmit public class BackupManagerServiceRoboTest { private static final String TAG = "BMSTest"; - private static final String TRANSPORT_NAME = - "com.google.android.gms/.backup.BackupTransportService"; @Mock private TransportManager mTransportManager; private HandlerThread mBackupThread; private ShadowLooper mShadowBackupLooper; private File mBaseStateDir; private File mDataDir; - private RefactoredBackupManagerService mBackupManagerService; private ShadowContextWrapper mShadowContext; private Context mContext; + private TransportData mTransport; + private String mTransportName; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mTransport = backupTransport(); + mTransportName = mTransport.transportName; + mBackupThread = new HandlerThread("backup-test"); mBackupThread.setUncaughtExceptionHandler( (t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e)); @@ -103,20 +112,14 @@ public class BackupManagerServiceRoboTest { mBaseStateDir = new File(cacheDir, "base_state_dir"); mDataDir = new File(cacheDir, "data_dir"); - mBackupManagerService = - new RefactoredBackupManagerService( - mContext, - new Trampoline(mContext), - mBackupThread, - mBaseStateDir, - mDataDir, - mTransportManager); + ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null); } @After public void tearDown() throws Exception { mBackupThread.quit(); ShadowAppBackupUtils.reset(); + ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null); } /* Tests for destination string */ @@ -124,10 +127,12 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenReturn("destinationString"); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME); + String destination = backupManagerService.getDestinationString(mTransportName); assertThat(destination).isEqualTo("destinationString"); } @@ -135,10 +140,12 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString_whenTransportNotRegistered() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME); + String destination = backupManagerService.getDestinationString(mTransportName); assertThat(destination).isNull(); } @@ -146,12 +153,14 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME)); + () -> backupManagerService.getDestinationString(mTransportName)); } /* Tests for app eligibility */ @@ -159,24 +168,28 @@ public class BackupManagerServiceRoboTest { @Test public void testIsAppEligibleForBackup_whenAppEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportData transport = - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport()); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - boolean result = mBackupManagerService.isAppEligibleForBackup("app.package"); + boolean result = backupManagerService.isAppEligibleForBackup("app.package"); assertThat(result).isTrue(); + verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } @Test public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - boolean result = mBackupManagerService.isAppEligibleForBackup("app.package"); + boolean result = backupManagerService.isAppEligibleForBackup("app.package"); assertThat(result).isFalse(); } @@ -184,38 +197,43 @@ public class BackupManagerServiceRoboTest { @Test public void testIsAppEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.isAppEligibleForBackup("app.package")); + () -> backupManagerService.isAppEligibleForBackup("app.package")); } @Test public void testFilterAppsEligibleForBackup() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportData transport = - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport); Map<String, Boolean> packagesMap = new HashMap<>(); packagesMap.put("package.a", true); packagesMap.put("package.b", false); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]); - String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages); + String[] filtered = backupManagerService.filterAppsEligibleForBackup(packages); assertThat(filtered).asList().containsExactly("package.a"); verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } @Test public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String[] filtered = - mBackupManagerService.filterAppsEligibleForBackup( + backupManagerService.filterAppsEligibleForBackup( new String[] {"package.a", "package.b"}); assertThat(filtered).isEmpty(); @@ -224,12 +242,14 @@ public class BackupManagerServiceRoboTest { @Test public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, () -> - mBackupManagerService.filterAppsEligibleForBackup( + backupManagerService.filterAppsEligibleForBackup( new String[] {"package.a", "package.b"})); } @@ -238,14 +258,14 @@ public class BackupManagerServiceRoboTest { private TransportData mNewTransport; private TransportData mOldTransport; private ComponentName mNewTransportComponent; - private ISelectBackupTransportCallback mCallback; + private ComponentName mOldTransportComponent; private void setUpForSelectTransport() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES); - mNewTransport = transports.get(0); - mNewTransportComponent = mNewTransport.transportClientMock.getTransportComponent(); - mOldTransport = transports.get(1); + mNewTransport = backupTransport(); + mNewTransportComponent = mNewTransport.getTransportComponent(); + mOldTransport = d2dTransport(); + mOldTransportComponent = mOldTransport.getTransportComponent(); + setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport()); when(mTransportManager.selectTransport(eq(mNewTransport.transportName))) .thenReturn(mOldTransport.transportName); } @@ -254,9 +274,11 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransport() throws Exception { setUpForSelectTransport(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String oldTransport = - mBackupManagerService.selectBackupTransport(mNewTransport.transportName); + backupManagerService.selectBackupTransport(mNewTransport.transportName); assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); assertThat(oldTransport).isEqualTo(mOldTransport.transportName); @@ -266,10 +288,12 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransport_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.selectBackupTransport(mNewTransport.transportName)); + () -> backupManagerService.selectBackupTransport(mNewTransport.transportName)); } @Test @@ -278,9 +302,29 @@ public class BackupManagerServiceRoboTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.SUCCESS); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); + + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); + verify(callback).onSuccess(eq(mNewTransport.transportName)); + } + + @Test + public void testSelectBackupTransportAsync_whenMandatoryTransport() throws Exception { + setUpForSelectTransport(); + ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(mNewTransportComponent); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) + .thenReturn(BackupManager.SUCCESS); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); @@ -288,14 +332,35 @@ public class BackupManagerServiceRoboTest { } @Test + public void testSelectBackupTransportAsync_whenOtherThanMandatoryTransport() + throws Exception { + setUpForSelectTransport(); + ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(mOldTransportComponent); + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) + .thenReturn(BackupManager.SUCCESS); + ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + + mShadowBackupLooper.runToEndOfTasks(); + assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName); + verify(callback).onFailure(eq(BackupManager.ERROR_BACKUP_NOT_ALLOWED)); + } + + @Test public void testSelectBackupTransportAsync_whenRegistrationFails() throws Exception { setUpForSelectTransport(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName); @@ -304,19 +369,19 @@ public class BackupManagerServiceRoboTest { @Test public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception { - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, null)); - ComponentName newTransportComponent = - TransportTestUtils.transportComponentName(TRANSPORT_NAME); + setUpTransports(mTransportManager, mTransport.unregistered()); + ComponentName newTransportComponent = mTransport.getTransportComponent(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent))) .thenReturn(BackupManager.SUCCESS); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(newTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(newTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); - assertThat(getSettingsTransport()).isNotEqualTo(TRANSPORT_NAME); + assertThat(getSettingsTransport()).isNotEqualTo(mTransportName); verify(callback).onFailure(anyInt()); } @@ -324,13 +389,14 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransportAsync_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - ComponentName newTransportComponent = - mNewTransport.transportClientMock.getTransportComponent(); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + ComponentName newTransportComponent = mNewTransport.getTransportComponent(); expectThrows( SecurityException.class, () -> - mBackupManagerService.selectBackupTransportAsync( + backupManagerService.selectBackupTransportAsync( newTransportComponent, mock(ISelectBackupTransportCallback.class))); } @@ -338,4 +404,55 @@ public class BackupManagerServiceRoboTest { return ShadowSettings.ShadowSecure.getString( mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); } + + /* Miscellaneous tests */ + + @Test + public void testConstructor_postRegisterTransports() { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + + createBackupManagerService(); + + mShadowBackupLooper.runToEndOfTasks(); + verify(mTransportManager).registerTransports(); + } + + @Test + public void testConstructor_doesNotRegisterTransportsSynchronously() { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + + createBackupManagerService(); + + // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks() + verify(mTransportManager, never()).registerTransports(); + } + + private RefactoredBackupManagerService createBackupManagerService() { + return new RefactoredBackupManagerService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + } + + private RefactoredBackupManagerService createInitializedBackupManagerService() { + RefactoredBackupManagerService backupManagerService = + new RefactoredBackupManagerService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + mShadowBackupLooper.runToEndOfTasks(); + // Handler instances have their own clock, so advancing looper (with runToEndOfTasks()) + // above does NOT advance the handlers' clock, hence whenever a handler post messages with + // specific time to the looper the time of those messages will be before the looper's time. + // To fix this we advance SystemClock as well since that is from where the handlers read + // time. + ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime()); + return backupManagerService; + } } diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java index acd670f6748c..cf0bc235dc10 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -16,108 +16,97 @@ package com.android.server.backup; +import static com.android.server.backup.testing.TransportData.genericTransport; +import static com.android.server.backup.testing.TransportTestUtils.mockTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager; + import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; import static org.testng.Assert.expectThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.concat; + import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.IBinder; -import android.os.RemoteException; import android.platform.test.annotations.Presubmit; -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.testing.ShadowBackupTransportStub; import com.android.server.backup.testing.ShadowContextImplForBackup; -import com.android.server.backup.testing.ShadowPackageManagerForBackup; -import com.android.server.backup.testing.TransportBoundListenerStub; +import com.android.server.testing.shadows.FrameworkShadowPackageManager; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; +import com.android.server.backup.transport.OnTransportRegisteredListener; import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.shadows.FrameworkShadowContextImpl; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowLog; -import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config( - manifest = Config.NONE, - sdk = 26, - shadows = { - ShadowContextImplForBackup.class, - ShadowBackupTransportStub.class, - ShadowPackageManagerForBackup.class - } + manifest = Config.NONE, + sdk = 26, + shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class} ) @SystemLoaderClasses({TransportManager.class}) @Presubmit public class TransportManagerTest { - private static final String PACKAGE_NAME = "some.package.name"; - private static final String ANOTHER_PACKAGE_NAME = "another.package.name"; + private static final String PACKAGE_A = "some.package.a"; + private static final String PACKAGE_B = "some.package.b"; - private TransportInfo mTransport1; - private TransportInfo mTransport2; + @Mock private OnTransportRegisteredListener mListener; + @Mock private TransportClientManager mTransportClientManager; + private TransportData mTransportA1; + private TransportData mTransportA2; + private TransportData mTransportB1; - private ShadowPackageManager mPackageManagerShadow; - - private final TransportBoundListenerStub mTransportBoundListenerStub = - new TransportBoundListenerStub(true); + private ShadowPackageManager mShadowPackageManager; + private Context mContext; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - ShadowLog.stream = System.out; - - mPackageManagerShadow = - (ShadowPackageManagerForBackup) + mShadowPackageManager = + (FrameworkShadowPackageManager) extract(RuntimeEnvironment.application.getPackageManager()); + mContext = RuntimeEnvironment.application.getApplicationContext(); - mTransport1 = new TransportInfo( - PACKAGE_NAME, - "transport1.name", - new Intent(), - "currentDestinationString", - new Intent(), - "dataManagementLabel"); - mTransport2 = new TransportInfo( - PACKAGE_NAME, - "transport2.name", - new Intent(), - "currentDestinationString", - new Intent(), - "dataManagementLabel"); - - ShadowContextImplForBackup.sComponentBinderMap.put(mTransport1.componentName, - mTransport1.binder); - ShadowContextImplForBackup.sComponentBinderMap.put(mTransport2.componentName, - mTransport2.binder); - ShadowBackupTransportStub.sBinderTransportMap.put( - mTransport1.binder, mTransport1.binderInterface); - ShadowBackupTransportStub.sBinderTransportMap.put( - mTransport2.binder, mTransport2.binderInterface); + mTransportA1 = genericTransport(PACKAGE_A, "TransportFoo"); + mTransportA2 = genericTransport(PACKAGE_A, "TransportBar"); + mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz"); } @After @@ -126,560 +115,441 @@ public class TransportManagerTest { } @Test - public void onPackageAdded_bindsToAllTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isTrue(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testRegisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2, mTransportB1); + TransportManager transportManager = + createTransportManager(mTransportA1, mTransportA2, mTransportB1); - @Test - public void onPackageAdded_oneTransportUnavailable_bindsToOnlyOneTransport() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - ShadowContextImplForBackup.sUnbindableComponents.add(mTransport1.componentName); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); + transportManager.registerTransports(); + + assertRegisteredTransports( + transportManager, asList(mTransportA1, mTransportA2, mTransportB1)); + + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportB1.transportName, mTransportB1.transportDirName); } @Test - public void onPackageAdded_whitelistIsNull_doesNotBindToTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); + public void + testRegisterTransports_whenOneTransportUnavailable_doesNotRegisterUnavailableTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + TransportData transport1 = mTransportA1.unavailable(); + TransportData transport2 = mTransportA2; + setUpTransports(transport1, transport2); + TransportManager transportManager = createTransportManager(transport1, transport2); + + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, singletonList(transport2)); + verify(mListener, never()) + .onTransportRegistered(transport1.transportName, transport1.transportDirName); + verify(mListener) + .onTransportRegistered(transport2.transportName, transport2.transportDirName); } @Test - public void onPackageAdded_onlyOneTransportWhitelisted_onlyConnectsToWhitelistedTransport() + public void testRegisterTransports_whenWhitelistIsEmpty_doesNotRegisterTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Collections.singleton(mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(null); - @Test - public void onPackageAdded_appIsNotPrivileged_doesNotBindToTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), 0); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, emptyList()); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void onPackageRemoved_transportsUnbound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void + testRegisterTransports_whenOnlyOneTransportWhitelisted_onlyRegistersWhitelistedTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(null, mTransportA1); - transportManager.onPackageRemoved(PACKAGE_NAME); + transportManager.registerTransports(); - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener, never()) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void onPackageRemoved_incorrectPackageName_nothingHappens() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testRegisterTransports_whenAppIsNotPrivileged_doesNotRegisterTransports() + throws Exception { + // Note ApplicationInfo.PRIVATE_FLAG_PRIVILEGED is missing from flags + setUpPackage(PACKAGE_A, 0); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = + createTransportManager(null, mTransportA1, mTransportA2); - transportManager.onPackageRemoved(ANOTHER_PACKAGE_NAME); + transportManager.registerTransports(); - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); + assertRegisteredTransports(transportManager, emptyList()); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void onPackageChanged_oneComponentChanged_onlyOneTransportRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testOnPackageAdded_registerTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); - @Test - public void onPackageChanged_nothingChanged_noTransportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[0]); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isFalse(); - } + transportManager.onPackageAdded(PACKAGE_A); - @Test - public void onPackageChanged_unexpectedComponentChanged_noTransportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{"unexpected.component"}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isFalse(); + assertRegisteredTransports(transportManager, asList(mTransportA1)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); } @Test - public void onPackageChanged_transportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } + public void testOnPackageRemoved_unregisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportB1); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportB1); + transportManager.registerTransports(); - @Test - public void getTransportBinder_returnsCorrectBinder() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getTransportBinder(mTransport1.name)).isEqualTo( - mTransport1.binderInterface); - assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( - mTransport2.binderInterface); + transportManager.onPackageRemoved(PACKAGE_A); + + assertRegisteredTransports(transportManager, singletonList(mTransportB1)); } @Test - public void getTransportBinder_incorrectTransportName_returnsNull() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testOnPackageRemoved_whenUnknownPackage_nothingHappens() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + + transportManager.onPackageRemoved(PACKAGE_A + "unknown"); - assertThat(transportManager.getTransportBinder("incorrect.transport")).isNull(); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); } @Test - public void getTransportBinder_oneTransportUnavailable_returnsCorrectBinder() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport1.name); + public void testOnPackageChanged_whenOneComponentChanged_onlyOneTransportReRegistered() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + // Reset listener to verify calls after registerTransports() above + reset(mListener); + + transportManager.onPackageChanged( + PACKAGE_A, mTransportA1.getTransportComponent().getClassName()); - assertThat(transportManager.getTransportBinder(mTransport1.name)).isNull(); - assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( - mTransport2.binderInterface); + assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener, never()) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void getCurrentTransport_selectTransportNotCalled_returnsDefaultTransport() + public void testOnPackageChanged_whenNoComponentsChanged_doesNotRegisterTransports() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + reset(mListener); - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + transportManager.onPackageChanged(PACKAGE_A); + + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void getCurrentTransport_selectTransportCalled_returnsCorrectTransport() + public void testOnPackageChanged_whenUnknownComponentChanged_noTransportsRegistered() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + reset(mListener); - transportManager.selectTransport(mTransport2.name); + transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A + ".UnknownComponent"); - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport2.name); + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void getCurrentTransportBinder_returnsCorrectBinder() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testOnPackageChanged_reRegisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + reset(mListener); - assertThat(transportManager.getCurrentTransportBinder()) - .isEqualTo(mTransport1.binderInterface); + transportManager.onPackageChanged( + PACKAGE_A, + mTransportA1.getTransportComponent().getClassName(), + mTransportA2.getTransportComponent().getClassName()); + + assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void getCurrentTransportBinder_transportNotBound_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport2.name); + public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); - transportManager.selectTransport(mTransport1.name); + String currentTransportName = transportManager.getCurrentTransportName(); - assertThat(transportManager.getCurrentTransportBinder()).isNull(); + assertThat(currentTransportName).isEqualTo(mTransportA1.transportName); } @Test - public void getTransportName_returnsCorrectTransportName() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getTransportName(mTransport1.binderInterface)) - .isEqualTo(mTransport1.name); - assertThat(transportManager.getTransportName(mTransport2.binderInterface)) - .isEqualTo(mTransport2.name); - } + public void testGetCurrentTransport_whenSelectTransportCalled_returnsSelectedTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + transportManager.selectTransport(mTransportA2.transportName); - @Test - public void getTransportName_transportNotBound_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport1.name); + String currentTransportName = transportManager.getCurrentTransportName(); - assertThat(transportManager.getTransportName(mTransport1.binderInterface)).isNull(); - assertThat(transportManager.getTransportName(mTransport2.binderInterface)) - .isEqualTo(mTransport2.name); + assertThat(currentTransportName).isEqualTo(mTransportA2.transportName); } @Test - public void getTransportWhitelist_returnsCorrectWhiteList() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)), - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.getTransportWhitelist()).containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - } + public void testGetTransportWhitelist() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); - @Test - public void getTransportWhitelist_whiteListIsNull_returnsEmptyArray() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.getTransportWhitelist()).isEmpty(); + Set<ComponentName> transportWhitelist = transportManager.getTransportWhitelist(); + + assertThat(transportWhitelist) + .containsExactlyElementsIn( + asList( + mTransportA1.getTransportComponent(), + mTransportA2.getTransportComponent())); } @Test - public void selectTransport_setsTransportCorrectlyAndReturnsPreviousTransport() - throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.selectTransport(mTransport2.name)).isEqualTo(mTransport1.name); - assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name); + public void testSelectTransport() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = + createTransportManager(null, mTransportA1, mTransportA2); + + String transport1 = transportManager.selectTransport(mTransportA1.transportName); + String transport2 = transportManager.selectTransport(mTransportA2.transportName); + + assertThat(transport1).isNull(); + assertThat(transport2).isEqualTo(mTransportA1.transportName); } @Test - public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testGetTransportClient_forRegisteredTransport() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); TransportClient transportClient = - transportManager.getTransportClient(mTransport1.name, "caller"); + transportManager.getTransportClient(mTransportA1.transportName, "caller"); - assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName); + assertThat(transportClient.getTransportComponent()) + .isEqualTo(mTransportA1.getTransportComponent()); } @Test - public void getTransportClient_forOldNameOfTransportThatChangedName_returnsNull() + public void testGetTransportClient_forOldNameOfTransportThatChangedName_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); TransportClient transportClient = - transportManager.getTransportClient(mTransport1.name, "caller"); + transportManager.getTransportClient(mTransportA1.transportName, "caller"); assertThat(transportClient).isNull(); } @Test - public void getTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly() + public void testGetTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); - TransportClient transportClient = - transportManager.getTransportClient("newName", "caller"); + TransportClient transportClient = transportManager.getTransportClient("newName", "caller"); - assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName); + assertThat(transportClient.getTransportComponent()) + .isEqualTo(mTransportA1.getTransportComponent()); } @Test - public void getTransportName_forTransportThatChangedName_returnsNewName() - throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testGetTransportName_forTransportThatChangedName_returnsNewName() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); - String transportName = transportManager.getTransportName(mTransport1.componentName); + String transportName = + transportManager.getTransportName(mTransportA1.getTransportComponent()); assertThat(transportName).isEqualTo("newName"); } @Test - public void isTransportRegistered_returnsCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - Collections.singletonList(mTransport2), - mTransport1.name); + public void testIsTransportRegistered() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + + boolean isTransportA1Registered = + transportManager.isTransportRegistered(mTransportA1.transportName); + boolean isTransportA2Registered = + transportManager.isTransportRegistered(mTransportA2.transportName); - assertThat(transportManager.isTransportRegistered(mTransport1.name)).isTrue(); - assertThat(transportManager.isTransportRegistered(mTransport2.name)).isFalse(); + assertThat(isTransportA1Registered).isTrue(); + assertThat(isTransportA2Registered).isFalse(); } @Test - public void getTransportAttributes_forRegisteredTransport_returnsCorrectValues() + public void testGetTransportAttributes_forRegisteredTransport_returnsCorrectValues() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - mTransport1.name); - - assertThat(transportManager.getTransportConfigurationIntent(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.configurationIntent()); - assertThat(transportManager.getTransportDataManagementIntent(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.dataManagementIntent()); - assertThat(transportManager.getTransportDataManagementLabel(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.dataManagementLabel()); - assertThat(transportManager.getTransportDirName(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.transportDirName()); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + + Intent configurationIntent = + transportManager.getTransportConfigurationIntent(mTransportA1.transportName); + Intent dataManagementIntent = + transportManager.getTransportDataManagementIntent(mTransportA1.transportName); + String dataManagementLabel = + transportManager.getTransportDataManagementLabel(mTransportA1.transportName); + String transportDirName = transportManager.getTransportDirName(mTransportA1.transportName); + + assertThat(configurationIntent).isEqualTo(mTransportA1.configurationIntent); + assertThat(dataManagementIntent).isEqualTo(mTransportA1.dataManagementIntent); + assertThat(dataManagementLabel).isEqualTo(mTransportA1.dataManagementLabel); + assertThat(transportDirName).isEqualTo(mTransportA1.transportDirName); } @Test - public void getTransportAttributes_forUnregisteredTransport_throws() - throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - Collections.singletonList(mTransport2), - mTransport1.name); + public void testGetTransportAttributes_forUnregisteredTransport_throws() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportConfigurationIntent(mTransport2.name)); + () -> transportManager.getTransportConfigurationIntent(mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDataManagementIntent( - mTransport2.name)); + () -> + transportManager.getTransportDataManagementIntent( + mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDataManagementLabel(mTransport2.name)); + () -> transportManager.getTransportDataManagementLabel(mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDirName(mTransport2.name)); + () -> transportManager.getTransportDirName(mTransportA2.transportName)); + } + + private List<TransportMock> setUpTransports(TransportData... transports) throws Exception { + setUpTransportsForTransportManager(mShadowPackageManager, transports); + List<TransportMock> transportMocks = new ArrayList<>(transports.length); + for (TransportData transport : transports) { + TransportMock transportMock = mockTransport(transport); + when(mTransportClientManager.getTransportClient( + eq(transport.getTransportComponent()), any())) + .thenReturn(transportMock.transportClient); + transportMocks.add(transportMock); + } + return transportMocks; } - private void setUpPackageWithTransports(String packageName, List<TransportInfo> transports, - int flags) throws Exception { + private void setUpPackage(String packageName, int flags) { PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = packageName; packageInfo.applicationInfo = new ApplicationInfo(); packageInfo.applicationInfo.privateFlags = flags; - - mPackageManagerShadow.addPackage(packageInfo); - - List<ResolveInfo> transportsInfo = new ArrayList<>(); - for (TransportInfo transport : transports) { - ResolveInfo info = new ResolveInfo(); - info.serviceInfo = new ServiceInfo(); - info.serviceInfo.packageName = packageName; - info.serviceInfo.name = transport.name; - transportsInfo.add(info); - } - - Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); - intent.setPackage(packageName); - - mPackageManagerShadow.addResolveInfoForIntent(intent, transportsInfo); - } - - private TransportManager createTransportManagerAndSetUpTransports( - List<TransportInfo> availableTransports, String defaultTransportName) throws Exception { - return createTransportManagerAndSetUpTransports(availableTransports, - Collections.<TransportInfo>emptyList(), defaultTransportName); + mShadowPackageManager.addPackage(packageInfo); } - private TransportManager createTransportManagerAndSetUpTransports( - List<TransportInfo> availableTransports, List<TransportInfo> unavailableTransports, - String defaultTransportName) - throws Exception { - List<String> availableTransportsNames = new ArrayList<>(); - List<ComponentName> availableTransportsComponentNames = new ArrayList<>(); - for (TransportInfo transport : availableTransports) { - availableTransportsNames.add(transport.name); - availableTransportsComponentNames.add(transport.componentName); - } - - List<ComponentName> allTransportsComponentNames = new ArrayList<>(); - allTransportsComponentNames.addAll(availableTransportsComponentNames); - for (TransportInfo transport : unavailableTransports) { - allTransportsComponentNames.add(transport.componentName); - } - - for (TransportInfo transport : unavailableTransports) { - ShadowContextImplForBackup.sUnbindableComponents.add(transport.componentName); - } - - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(allTransportsComponentNames), - defaultTransportName, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - availableTransportsComponentNames); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - availableTransportsNames); - for (TransportInfo transport : availableTransports) { - assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface)) - .isTrue(); - } - for (TransportInfo transport : unavailableTransports) { - assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface)) - .isFalse(); - } - - mTransportBoundListenerStub.resetState(); - + private TransportManager createTransportManager( + @Nullable TransportData selectedTransport, TransportData... transports) { + Set<ComponentName> whitelist = + concat(Stream.of(selectedTransport), Stream.of(transports)) + .filter(Objects::nonNull) + .map(TransportData::getTransportComponent) + .collect(toSet()); + TransportManager transportManager = + new TransportManager( + mContext, + whitelist, + selectedTransport != null ? selectedTransport.transportName : null, + mTransportClientManager); + transportManager.setOnTransportRegisteredListener(mListener); return transportManager; } - private static class TransportInfo { - public final String packageName; - public final String name; - public final ComponentName componentName; - public final IBackupTransport binderInterface; - public final IBinder binder; - - TransportInfo( - String packageName, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - String dataManagementLabel) { - this.packageName = packageName; - this.name = name; - this.componentName = new ComponentName(packageName, name); - this.binder = mock(IBinder.class); - IBackupTransport transport = mock(IBackupTransport.class); - try { - when(transport.name()).thenReturn(name); - when(transport.configurationIntent()).thenReturn(configurationIntent); - when(transport.currentDestinationString()).thenReturn(currentDestinationString); - when(transport.dataManagementIntent()).thenReturn(dataManagementIntent); - when(transport.dataManagementLabel()).thenReturn(dataManagementLabel); - } catch (RemoteException e) { - // Only here to mock methods that throw RemoteException - } - this.binderInterface = transport; - } + private void assertRegisteredTransports( + TransportManager transportManager, List<TransportData> transports) { + assertThat(transportManager.getRegisteredTransportComponents()) + .asList() + .containsExactlyElementsIn( + transports + .stream() + .map(TransportData::getTransportComponent) + .collect(toList())); + assertThat(transportManager.getRegisteredTransportNames()) + .asList() + .containsExactlyElementsIn( + transports.stream().map(t -> t.transportName).collect(toList())); } - } diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java index dfca9010130f..ace0441c8a4a 100644 --- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java +++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -19,8 +19,10 @@ package com.android.server.backup.internal; import static android.app.backup.BackupTransport.TRANSPORT_ERROR; import static android.app.backup.BackupTransport.TRANSPORT_OK; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAME; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES; +import static com.android.server.backup.testing.TransportData.backupTransport; +import static com.android.server.backup.testing.TransportData.d2dTransport; +import static com.android.server.backup.testing.TransportData.localTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransports; import static com.google.common.truth.Truth.assertThat; @@ -28,7 +30,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,7 +45,8 @@ import com.android.internal.backup.IBackupTransport; import com.android.server.backup.RefactoredBackupManagerService; import com.android.server.backup.TransportManager; import com.android.server.backup.testing.TransportTestUtils; -import com.android.server.backup.testing.TransportTestUtils.TransportData; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportClient; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; @@ -58,7 +60,10 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.io.File; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) @@ -68,16 +73,21 @@ public class PerformInitializeTaskTest { @Mock private RefactoredBackupManagerService mBackupManagerService; @Mock private TransportManager mTransportManager; @Mock private OnTaskFinishedListener mListener; - @Mock private IBackupTransport mTransport; + @Mock private IBackupTransport mTransportBinder; @Mock private IBackupObserver mObserver; @Mock private AlarmManager mAlarmManager; @Mock private PendingIntent mRunInitIntent; private File mBaseStateDir; + private TransportData mTransport; + private String mTransportName; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mTransport = backupTransport(); + mTransportName = mTransport.transportName; + Application context = RuntimeEnvironment.application; mBaseStateDir = new File(context.getCacheDir(), "base_state_dir"); assertThat(mBaseStateDir.mkdir()).isTrue(); @@ -88,82 +98,76 @@ public class PerformInitializeTaskTest { @Test public void testRun_callsTransportCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder).finishBackup(); } @Test public void testRun_callsBackupManagerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); verify(mBackupManagerService) - .recordInitPending( - false, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(false, mTransportName, mTransport.transportDirName); verify(mBackupManagerService) - .resetBackupState( - eq( - new File( - mBaseStateDir, - TransportTestUtils.transportDirName(TRANSPORT_NAME)))); + .resetBackupState(eq(new File(mBaseStateDir, mTransport.transportDirName))); } @Test public void testRun_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_OK)); verify(mObserver).backupFinished(eq(TRANSPORT_OK)); verify(mListener).onFinished(any()); } @Test public void testRun_whenInitializeDeviceFails() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport, never()).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder, never()).finishBackup(); verify(mBackupManagerService) - .recordInitPending( - true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(true, mTransportName, mTransport.transportDirName); } @Test public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -172,37 +176,36 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenFinishBackupFails() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder).finishBackup(); verify(mBackupManagerService) - .recordInitPending( - true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(true, mTransportName, mTransport.transportDirName); } @Test public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -211,64 +214,76 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenOnlyOneTransportFails() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); - configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + TransportData transport1 = backupTransport(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); - verify(transports.get(1).transportMock).initializeDevice(); - verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR)); - verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK)); + verify(transportMocks.get(1).transport).initializeDevice(); + verify(mObserver).onResult(eq(transport1.transportName), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(transport2.transportName), eq(TRANSPORT_OK)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); } @Test public void testRun_withMultipleTransports() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES); - configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); - configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES); + List<TransportMock> transportMocks = + setUpTransports( + mTransportManager, backupTransport(), d2dTransport(), localTransport()); + configureTransport(transportMocks.get(0).transport, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transportMocks.get(2).transport, TRANSPORT_OK, TRANSPORT_OK); + String[] transportNames = + Stream.of(new TransportData[] {backupTransport(), d2dTransport(), localTransport()}) + .map(t -> t.transportName) + .toArray(String[]::new); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(transportNames); performInitializeTask.run(); - for (TransportData transport : transports) { + Iterator<TransportData> transportsIterator = + Arrays.asList( + new TransportData[] { + backupTransport(), d2dTransport(), localTransport() + }) + .iterator(); + for (TransportMock transportMock : transportMocks) { + TransportData transport = transportsIterator.next(); verify(mTransportManager).getTransportClient(eq(transport.transportName), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } } @Test public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); - configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0); - configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK); + TransportData transport1 = backupTransport(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); verify(mTransportManager) - .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any()); + .disposeOfTransportClient(eq(transportMocks.get(0).transportClient), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any()); + .disposeOfTransportClient(eq(transportMocks.get(1).transportClient), any()); } @Test public void testRun_whenTransportNotRegistered() throws Exception { - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, null)); - - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransports(mTransportManager, mTransport.unregistered()); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -279,16 +294,15 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenOnlyOneTransportNotRegistered() throws Exception { - List<TransportData> transports = - TransportTestUtils.setUpTransports( - mTransportManager, - new TransportData(TRANSPORT_NAMES[0], null, null), - new TransportData(TRANSPORT_NAMES[1])); - String registeredTransportName = transports.get(1).transportName; - IBackupTransport registeredTransport = transports.get(1).transportMock; - TransportClient registeredTransportClient = transports.get(1).transportClientMock; + TransportData transport1 = backupTransport().unregistered(); + TransportData transport2 = d2dTransport(); + List<TransportMock> transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + String registeredTransportName = transport2.transportName; + IBackupTransport registeredTransport = transportMocks.get(1).transport; + TransportClient registeredTransportClient = transportMocks.get(1).transportClient; PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); @@ -299,25 +313,24 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenTransportNotAvailable() throws Exception { - TransportClient transportClient = mock(TransportClient.class); - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient)); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + TransportMock transportMock = setUpTransport(mTransport.unavailable()); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mTransportManager) + .disposeOfTransportClient(eq(transportMock.transportClient), any()); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenTransportThrowsDeadObjectException() throws Exception { - TransportClient transportClient = mock(TransportClient.class); - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient)); - when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + TransportMock transportMock = setUpTransport(mTransport); + IBackupTransport transport = transportMock.transport; + TransportClient transportClient = transportMock.transportClient; + when(transport.initializeDevice()).thenThrow(DeadObjectException.class); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -343,9 +356,10 @@ public class PerformInitializeTaskTest { when(transportMock.finishBackup()).thenReturn(finishBackupStatus); } - private void setUpTransport(String transportName) throws Exception { - TransportTestUtils.setUpTransport( - mTransportManager, - new TransportData(transportName, mTransport, mock(TransportClient.class))); + private TransportMock setUpTransport(TransportData transport) throws Exception { + TransportMock transportMock = + TransportTestUtils.setUpTransport(mTransportManager, transport); + mTransportBinder = transportMock.transport; + return transportMock; } } diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java b/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java new file mode 100644 index 000000000000..88b30da46433 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/ShadowBackupPolicyEnforcer.java @@ -0,0 +1,24 @@ +package com.android.server.backup.testing; + +import android.content.ComponentName; + +import com.android.server.backup.BackupPolicyEnforcer; +import com.android.server.backup.RefactoredBackupManagerService; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(BackupPolicyEnforcer.class) +public class ShadowBackupPolicyEnforcer { + + private static ComponentName sMandatoryBackupTransport; + + public static void setMandatoryBackupTransport(ComponentName backupTransportComponent) { + sMandatoryBackupTransport = backupTransportComponent; + } + + @Implementation + public ComponentName getMandatoryBackupTransport() { + return sMandatoryBackupTransport; + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java new file mode 100644 index 000000000000..1be298d2c8c5 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 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.backup.testing; + +import com.android.internal.util.FunctionalUtils.ThrowingRunnable; + +import java.util.concurrent.Callable; + +public class TestUtils { + /** + * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the + * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and + * throw. + * + * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure + * in a test. + */ + public static void uncheck(ThrowingRunnable runnable) { + try { + runnable.runOrThrow(); + } catch (Exception e) { + throw wrapIfChecked(e); + } + } + + /** + * Calls {@link Callable#call()} and returns the value if no exception is thrown. Otherwise, if + * the exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} + * and throw. + * + * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure + * in a test. + */ + public static <T> T uncheck(Callable<T> callable) { + try { + return callable.call(); + } catch (Exception e) { + throw wrapIfChecked(e); + } + } + + /** + * Wrap {@code e} in a {@link RuntimeException} only if it's not one already, in which case it's + * returned. + */ + public static RuntimeException wrapIfChecked(Exception e) { + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + return new RuntimeException(e); + } + + private TestUtils() {} +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java deleted file mode 100644 index 84ac2c212854..000000000000 --- a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java +++ /dev/null @@ -1,64 +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.server.backup.testing; - -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.TransportManager; - -import java.util.HashSet; -import java.util.Set; - -/** - * Stub implementation of TransportBoundListener, which returns given result and can tell whether - * it was called for given transport. - */ -public class TransportBoundListenerStub implements - TransportManager.TransportBoundListener { - private boolean mAlwaysReturnSuccess; - private Set<IBackupTransport> mTransportsCalledFor = new HashSet<>(); - - public TransportBoundListenerStub(boolean alwaysReturnSuccess) { - this.mAlwaysReturnSuccess = alwaysReturnSuccess; - } - - @Override - public boolean onTransportBound(IBackupTransport binder) { - mTransportsCalledFor.add(binder); - return mAlwaysReturnSuccess; - } - - /** - * Returns whether the listener was called for the specified transport at least once. - */ - public boolean isCalledForTransport(IBackupTransport binder) { - return mTransportsCalledFor.contains(binder); - } - - /** - * Returns whether the listener was called at least once. - */ - public boolean isCalled() { - return !mTransportsCalledFor.isEmpty(); - } - - /** - * Resets listener calls. - */ - public void resetState() { - mTransportsCalledFor.clear(); - } -} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/src/com/android/server/backup/testing/TransportData.java new file mode 100644 index 000000000000..9feaa8efcf3d --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TransportData.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 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.backup.testing; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Intent; + +public class TransportData { + // No constants since new Intent() can't be called in static context because of Robolectric + public static TransportData backupTransport() { + return new TransportData( + "com.google.android.gms/.backup.BackupTransportService", + "com.google.android.gms/.backup.BackupTransportService", + "com.google.android.gms.backup.BackupTransportService", + new Intent(), + "user@gmail.com", + new Intent(), + "Google Account"); + } + + public static TransportData d2dTransport() { + return new TransportData( + "com.google.android.gms/.backup.migrate.service.D2dTransport", + "com.google.android.gms/.backup.component.D2dTransportService", + "d2dMigrateTransport", + null, + "Moving data to new device", + null, + ""); + } + + public static TransportData localTransport() { + return new TransportData( + "android/com.android.internal.backup.LocalTransport", + "android/com.android.internal.backup.LocalTransportService", + "com.android.internal.backup.LocalTransport", + null, + "Backing up to debug-only private cache", + null, + ""); + } + + public static TransportData genericTransport(String packageName, String className) { + return new TransportData( + packageName + "/." + className, + packageName + "/." + className + "Service", + packageName + "." + className, + new Intent(), + "currentDestinationString", + new Intent(), + "dataManagementLabel"); + } + + @TransportTestUtils.TransportStatus + public int transportStatus; + public final String transportName; + private final String transportComponentShort; + @Nullable + public String transportDirName; + @Nullable public Intent configurationIntent; + @Nullable public String currentDestinationString; + @Nullable public Intent dataManagementIntent; + @Nullable public String dataManagementLabel; + + private TransportData( + @TransportTestUtils.TransportStatus int transportStatus, + String transportName, + String transportComponentShort, + String transportDirName, + Intent configurationIntent, + String currentDestinationString, + Intent dataManagementIntent, + String dataManagementLabel) { + this.transportStatus = transportStatus; + this.transportName = transportName; + this.transportComponentShort = transportComponentShort; + this.transportDirName = transportDirName; + this.configurationIntent = configurationIntent; + this.currentDestinationString = currentDestinationString; + this.dataManagementIntent = dataManagementIntent; + this.dataManagementLabel = dataManagementLabel; + } + + public TransportData( + String transportName, + String transportComponentShort, + String transportDirName, + Intent configurationIntent, + String currentDestinationString, + Intent dataManagementIntent, + String dataManagementLabel) { + this( + TransportTestUtils.TransportStatus.REGISTERED_AVAILABLE, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + + /** + * Not field because otherwise we'd have to call ComponentName::new in static context and + * Robolectric does not like this. + */ + public ComponentName getTransportComponent() { + return ComponentName.unflattenFromString(transportComponentShort); + } + + public TransportData unavailable() { + return new TransportData( + TransportTestUtils.TransportStatus.REGISTERED_UNAVAILABLE, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + + public TransportData unregistered() { + return new TransportData( + TransportTestUtils.TransportStatus.UNREGISTERED, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java index 9770e407ec72..e1dc7b5e151e 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java @@ -16,13 +16,23 @@ package com.android.server.backup.testing; +import static com.android.server.backup.testing.TestUtils.uncheck; + +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static java.util.stream.Collectors.toList; + import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.RemoteException; +import android.support.annotation.IntDef; import com.android.internal.backup.IBackupTransport; import com.android.server.backup.TransportManager; @@ -30,85 +40,82 @@ import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; -import java.util.Arrays; +import org.robolectric.shadows.ShadowPackageManager; + import java.util.List; +import java.util.stream.Stream; public class TransportTestUtils { - public static final String[] TRANSPORT_NAMES = { - "android/com.android.internal.backup.LocalTransport", - "com.google.android.gms/.backup.migrate.service.D2dTransport", - "com.google.android.gms/.backup.BackupTransportService" - }; + /** + * Differently from {@link #setUpTransports(TransportManager, TransportData...)}, which + * configures {@link TransportManager}, this is meant to mock the environment for a real + * TransportManager. + */ + public static void setUpTransportsForTransportManager( + ShadowPackageManager shadowPackageManager, TransportData... transports) + throws Exception { + for (TransportData transport : transports) { + ComponentName transportComponent = transport.getTransportComponent(); + String packageName = transportComponent.getPackageName(); + ResolveInfo resolveInfo = resolveInfo(transportComponent); + shadowPackageManager.addResolveInfoForIntent(transportIntent(), resolveInfo); + shadowPackageManager.addResolveInfoForIntent( + transportIntent().setPackage(packageName), resolveInfo); + } + } - public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0]; + private static Intent transportIntent() { + return new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); + } - /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static TransportData setUpCurrentTransport( - TransportManager transportManager, String transportName) throws Exception { - TransportData transport = setUpTransports(transportManager, transportName).get(0); - when(transportManager.getCurrentTransportClient(any())) - .thenReturn(transport.transportClientMock); - return transport; + private static ResolveInfo resolveInfo(ComponentName transportComponent) { + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = new ServiceInfo(); + resolveInfo.serviceInfo.packageName = transportComponent.getPackageName(); + resolveInfo.serviceInfo.name = transportComponent.getClassName(); + return resolveInfo; } /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static List<TransportData> setUpTransports( - TransportManager transportManager, String... transportNames) throws Exception { - return setUpTransports( - transportManager, - Arrays.stream(transportNames) - .map(TransportData::new) - .toArray(TransportData[]::new)); + public static TransportMock setUpCurrentTransport( + TransportManager transportManager, TransportData transport) throws Exception { + TransportMock transportMock = setUpTransports(transportManager, transport).get(0); + if (transportMock.transportClient != null) { + when(transportManager.getCurrentTransportClient(any())) + .thenReturn(transportMock.transportClient); + } + return transportMock; } /** @see #setUpTransport(TransportManager, TransportData) */ - public static List<TransportData> setUpTransports( + public static List<TransportMock> setUpTransports( TransportManager transportManager, TransportData... transports) throws Exception { - for (TransportData transport : transports) { - setUpTransport(transportManager, transport); - } - return Arrays.asList(transports); + return Stream.of(transports) + .map(transport -> uncheck(() -> setUpTransport(transportManager, transport))) + .collect(toList()); } - /** - * Configures transport according to {@link TransportData}: - * - * <ul> - * <li>{@link TransportData#transportMock} {@code null} means transport not available. - * <li>{@link TransportData#transportClientMock} {@code null} means transport not registered. - * </ul> - */ - public static void setUpTransport(TransportManager transportManager, TransportData transport) - throws Exception { + public static TransportMock setUpTransport( + TransportManager transportManager, TransportData transport) throws Exception { + int status = transport.transportStatus; String transportName = transport.transportName; - String transportDirName = transportDirName(transportName); - ComponentName transportComponent = transportComponentName(transportName); - IBackupTransport transportMock = transport.transportMock; - TransportClient transportClientMock = transport.transportClientMock; + ComponentName transportComponent = transport.getTransportComponent(); + String transportDirName = transport.transportDirName; - if (transportClientMock != null) { + TransportMock transportMock = mockTransport(transport); + if (status == TransportStatus.REGISTERED_AVAILABLE + || status == TransportStatus.REGISTERED_UNAVAILABLE) { // Transport registered when(transportManager.getTransportClient(eq(transportName), any())) - .thenReturn(transportClientMock); + .thenReturn(transportMock.transportClient); when(transportManager.getTransportClientOrThrow(eq(transportName), any())) - .thenReturn(transportClientMock); + .thenReturn(transportMock.transportClient); when(transportManager.getTransportName(transportComponent)).thenReturn(transportName); when(transportManager.getTransportDirName(eq(transportName))) .thenReturn(transportDirName); when(transportManager.getTransportDirName(eq(transportComponent))) .thenReturn(transportDirName); - when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); - - if (transportMock != null) { - // Transport registered and available - when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); - when(transportMock.name()).thenReturn(transportName); - when(transportMock.transportDirName()).thenReturn(transportDirName); - } else { - // Transport registered but unavailable - when(transportClientMock.connectOrThrow(any())) - .thenThrow(TransportNotAvailableException.class); - } + // TODO: Mock rest of description methods } else { // Transport not registered when(transportManager.getTransportClient(eq(transportName), any())).thenReturn(null); @@ -121,35 +128,74 @@ public class TransportTestUtils { when(transportManager.getTransportDirName(eq(transportComponent))) .thenThrow(TransportNotRegisteredException.class); } + return transportMock; } - /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static ComponentName transportComponentName(String transportName) { - return ComponentName.unflattenFromString(transportName); - } + public static TransportMock mockTransport(TransportData transport) throws Exception { + final TransportClient transportClientMock; + int status = transport.transportStatus; + ComponentName transportComponent = transport.getTransportComponent(); + if (status == TransportStatus.REGISTERED_AVAILABLE + || status == TransportStatus.REGISTERED_UNAVAILABLE) { + // Transport registered + transportClientMock = mock(TransportClient.class); + when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); + if (status == TransportStatus.REGISTERED_AVAILABLE) { + // Transport registered and available + IBackupTransport transportMock = mockTransportBinder(transport); + when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); + + return new TransportMock(transportClientMock, transportMock); + } else { + // Transport registered but unavailable + when(transportClientMock.connectOrThrow(any())) + .thenThrow(TransportNotAvailableException.class); - public static String transportDirName(String transportName) { - return transportName + "_dir_name"; + return new TransportMock(transportClientMock, null); + } + } else { + // Transport not registered + return new TransportMock(null, null); + } } - public static class TransportData { - public final String transportName; - @Nullable public final IBackupTransport transportMock; - @Nullable public final TransportClient transportClientMock; - - public TransportData( - String transportName, - @Nullable IBackupTransport transportMock, - @Nullable TransportClient transportClientMock) { - this.transportName = transportName; - this.transportMock = transportMock; - this.transportClientMock = transportClientMock; + private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception { + IBackupTransport transportBinder = mock(IBackupTransport.class); + try { + when(transportBinder.name()).thenReturn(transport.transportName); + when(transportBinder.transportDirName()).thenReturn(transport.transportDirName); + when(transportBinder.configurationIntent()).thenReturn(transport.configurationIntent); + when(transportBinder.currentDestinationString()) + .thenReturn(transport.currentDestinationString); + when(transportBinder.dataManagementIntent()).thenReturn(transport.dataManagementIntent); + when(transportBinder.dataManagementLabel()).thenReturn(transport.dataManagementLabel); + } catch (RemoteException e) { + fail("RemoteException?"); } + return transportBinder; + } + + public static class TransportMock { + @Nullable public final TransportClient transportClient; + @Nullable public final IBackupTransport transport; - public TransportData(String transportName) { - this(transportName, mock(IBackupTransport.class), mock(TransportClient.class)); + private TransportMock( + @Nullable TransportClient transportClient, @Nullable IBackupTransport transport) { + this.transportClient = transportClient; + this.transport = transport; } } + @IntDef({ + TransportStatus.REGISTERED_AVAILABLE, + TransportStatus.REGISTERED_UNAVAILABLE, + TransportStatus.UNREGISTERED + }) + public @interface TransportStatus { + int REGISTERED_AVAILABLE = 0; + int REGISTERED_UNAVAILABLE = 1; + int UNREGISTERED = 2; + } + private TransportTestUtils() {} } diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java new file mode 100644 index 000000000000..6d220737f2fb --- /dev/null +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 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.testing.shadows; + +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.UserHandle; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowContextImpl; + +@Implements(className = ShadowContextImpl.CLASS_NAME, inheritImplementationMethods = true) +public class FrameworkShadowContextImpl extends ShadowContextImpl { + @Implementation + public boolean bindServiceAsUser( + Intent service, + ServiceConnection connection, + int flags, + UserHandle user) { + return bindService(service, connection, flags); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java index b64b59d238d1..5cdbe7ff09ba 100644 --- a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java @@ -14,22 +14,18 @@ * limitations under the License */ -package com.android.server.backup.testing; +package com.android.server.testing.shadows; import android.app.ApplicationPackageManager; import android.content.Intent; import android.content.pm.ResolveInfo; - +import java.util.List; import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowApplicationPackageManager; -import java.util.List; - -/** - * Implementation of PackageManager for Robolectric which handles queryIntentServicesAsUser(). - */ +/** Extension of ShadowApplicationPackageManager */ @Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true) -public class ShadowPackageManagerForBackup extends ShadowApplicationPackageManager { +public class FrameworkShadowPackageManager extends ShadowApplicationPackageManager { @Override public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) { return queryIntentServices(intent, flags); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index b38a4136c94a..9923fa86758b 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -22,6 +22,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.am.ActivityStack.ActivityState.PAUSING; import static com.android.server.am.ActivityStack.ActivityState.STOPPED; import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING; @@ -122,7 +123,15 @@ public class ActivityRecordTests extends ActivityTestsBase { mActivity.makeVisibleIfNeeded(null /* starting */); assertEquals(mActivity.state, PAUSING); + assertTrue(pauseFound.value); + + // Make sure that the state does not change for current non-stopping states. + mActivity.state = INITIALIZING; + + mActivity.makeVisibleIfNeeded(null /* starting */); + + assertEquals(mActivity.state, INITIALIZING); } @Test diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index 766d30d9f9a5..7b4441ae30e6 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -96,7 +96,7 @@ public class BackupManagerServiceTest { createBackupManagerService(); verify(mTransportManager) - .setTransportBoundListener(any(TransportManager.TransportBoundListener.class)); + .setOnTransportRegisteredListener(any()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index 08edd52c7112..edc7d74b47cb 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -447,21 +447,24 @@ public class BrightnessTrackerTest { @Test public void testParcelUnParcel() { Parcel parcel = Parcel.obtain(); - BrightnessChangeEvent event = new BrightnessChangeEvent(); - event.brightness = 23f; - event.timeStamp = 345L; - event.packageName = "com.example"; - event.userId = 12; - event.luxValues = new float[2]; - event.luxValues[0] = 3000.0f; - event.luxValues[1] = 4000.0f; - event.luxTimestamps = new long[2]; - event.luxTimestamps[0] = 325L; - event.luxTimestamps[1] = 315L; - event.batteryLevel = 0.7f; - event.nightMode = false; - event.colorTemperature = 345; - event.lastBrightness = 50f; + BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder(); + builder.setBrightness(23f); + builder.setTimeStamp(345L); + builder.setPackageName("com.example"); + builder.setUserId(12); + float[] luxValues = new float[2]; + luxValues[0] = 3000.0f; + luxValues[1] = 4000.0f; + builder.setLuxValues(luxValues); + long[] luxTimestamps = new long[2]; + luxTimestamps[0] = 325L; + luxTimestamps[1] = 315L; + builder.setLuxTimestamps(luxTimestamps); + builder.setBatteryLevel(0.7f); + builder.setNightMode(false); + builder.setColorTemperature(345); + builder.setLastBrightness(50f); + BrightnessChangeEvent event = builder.build(); event.writeToParcel(parcel, 0); byte[] parceled = parcel.marshall(); @@ -485,7 +488,8 @@ public class BrightnessTrackerTest { assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA); parcel = Parcel.obtain(); - event.batteryLevel = Float.NaN; + builder.setBatteryLevel(Float.NaN); + event = builder.build(); event.writeToParcel(parcel, 0); parceled = parcel.marshall(); parcel.recycle(); diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java index 9a6da0e791b5..b6c370eb6b08 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java @@ -16,15 +16,16 @@ package com.android.server.policy; -import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -34,7 +35,6 @@ import android.graphics.PixelFormat; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.view.Surface; import android.view.WindowManager; import org.junit.Before; @@ -128,7 +128,23 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { } @Test - public void layoutWindowLw_withDisplayCutout_fullscreen() { + public void layoutWindowLw_withhDisplayCutout_never() { + addDisplayCutout(); + + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_layoutFullscreen() { addDisplayCutout(); mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; @@ -137,6 +153,22 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreen() { + addDisplayCutout(); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); @@ -147,8 +179,8 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() { addDisplayCutout(); - mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN; + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mPolicy.addWindow(mAppWindow); mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); @@ -217,7 +249,7 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { setRotation(ROTATION_90); mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + mAppWindow.attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mPolicy.addWindow(mAppWindow); mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); diff --git a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java index ce76a223ef20..ac291632c877 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DragDropControllerTests.java @@ -16,27 +16,38 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.content.ClipData; import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManagerInternal; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.view.InputChannel; import android.view.Surface; import android.view.SurfaceSession; +import android.view.View; +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; + /** * Tests for the {@link DragDropController} class. * @@ -46,36 +57,92 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @Presubmit public class DragDropControllerTests extends WindowTestsBase { - private static final int TIMEOUT_MS = 1000; - private DragDropController mTarget; + private static final int TIMEOUT_MS = 3000; + private TestDragDropController mTarget; private WindowState mWindow; private IBinder mToken; + static class TestDragDropController extends DragDropController { + @GuardedBy("sWm.mWindowMap") + private Runnable mCloseCallback; + + TestDragDropController(WindowManagerService service, Looper looper) { + super(service, looper); + } + + void setOnClosedCallbackLocked(Runnable runnable) { + assertTrue(dragDropActiveLocked()); + mCloseCallback = runnable; + } + + @Override + void onDragStateClosedLocked(DragState dragState) { + super.onDragStateClosedLocked(dragState); + if (mCloseCallback != null) { + mCloseCallback.run(); + mCloseCallback = null; + } + } + } + + /** + * Creates a window state which can be used as a drop target. + */ + private WindowState createDropTargetWindow(String name, int ownerId) { + final WindowTestUtils.TestAppWindowToken token = new WindowTestUtils.TestAppWindowToken( + mDisplayContent); + final TaskStack stack = createStackControllerOnStackOnDisplay( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; + final Task task = createTaskInStack(stack, ownerId); + task.addChild(token, 0); + + final WindowState window = createWindow( + null, TYPE_BASE_APPLICATION, token, name, ownerId, false); + window.mInputChannel = new InputChannel(); + window.mHasSurface = true; + return window; + } + @Before public void setUp() throws Exception { + final UserManagerInternal userManager = mock(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, userManager); + super.setUp(); - assertNotNull(sWm.mDragDropController); - mTarget = sWm.mDragDropController; - mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window"); + + mTarget = new TestDragDropController(sWm, sWm.mH.getLooper()); + mDisplayContent = spy(mDisplayContent); + mWindow = createDropTargetWindow("Drag test window", 0); + when(mDisplayContent.getTouchableWinAtPointLocked(0, 0)).thenReturn(mWindow); + when(sWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true); + synchronized (sWm.mWindowMap) { - // Because sWm is a static object, the previous operation may remain. - assertFalse(mTarget.dragDropActiveLocked()); + sWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); } } @After - public void tearDown() { - if (mToken != null) { - mTarget.cancelDragAndDrop(mToken); + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(UserManagerInternal.class); + final CountDownLatch latch; + synchronized (sWm.mWindowMap) { + if (!mTarget.dragDropActiveLocked()) { + return; + } + if (mToken != null) { + mTarget.cancelDragAndDrop(mToken); + } + latch = new CountDownLatch(1); + mTarget.setOnClosedCallbackLocked(() -> { + latch.countDown(); + }); } + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } @Test - public void testPrepareDrag() throws Exception { - final Surface surface = new Surface(); - mToken = mTarget.prepareDrag( - new SurfaceSession(), 0, 0, mWindow.mClient, 0, 100, 100, surface); - assertNotNull(mToken); + public void testDragFlow() throws Exception { + dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0); } @Test @@ -85,4 +152,33 @@ public class DragDropControllerTests extends WindowTestsBase { new SurfaceSession(), 0, 0, mWindow.mClient, 0, 0, 0, surface); assertNull(mToken); } + + @Test + public void testPerformDrag_NullDataWithGrantUri() throws Exception { + dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0); + } + + @Test + public void testPerformDrag_NullDataToOtherUser() throws Exception { + final WindowState otherUsersWindow = + createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE); + when(mDisplayContent.getTouchableWinAtPointLocked(10, 10)) + .thenReturn(otherUsersWindow); + + dragFlow(0, null, 10, 10); + } + + private void dragFlow(int flag, ClipData data, float dropX, float dropY) { + final Surface surface = new Surface(); + mToken = mTarget.prepareDrag( + new SurfaceSession(), 0, 0, mWindow.mClient, flag, 100, 100, surface); + assertNotNull(mToken); + + assertTrue(sWm.mInputManager.transferTouchFocus(null, null)); + assertTrue(mTarget.performDrag( + mWindow.mClient, mToken, 0, 0, 0, 0, 0, data)); + + mTarget.handleMotionEvent(false, dropX, dropY); + mToken = mWindow.mClient.asBinder(); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index c699a94db279..69b13787ef93 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -230,20 +230,22 @@ class WindowTestsBase { boolean ownerCanAddInternalSystemWindow) { final WindowToken token = createWindowToken( dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type); - return createWindow(parent, type, token, name, ownerCanAddInternalSystemWindow); + return createWindow(parent, type, token, name, 0 /* ownerId */, + ownerCanAddInternalSystemWindow); } static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) { - return createWindow(parent, type, token, name, false /* ownerCanAddInternalSystemWindow */); + return createWindow(parent, type, token, name, 0 /* ownerId */, + false /* ownerCanAddInternalSystemWindow */); } static WindowState createWindow(WindowState parent, int type, WindowToken token, String name, - boolean ownerCanAddInternalSystemWindow) { + int ownerId, boolean ownerCanAddInternalSystemWindow) { final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type); attrs.setTitle(name); final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent, OP_NONE, - 0, attrs, VISIBLE, 0, ownerCanAddInternalSystemWindow); + 0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow); // TODO: Probably better to make this call in the WindowState ctor to avoid errors with // adding it to the token... token.addWindow(w); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 5a1a3e353d70..ce0b551311b0 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -960,8 +960,9 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; /** - * String to identify carrier name in CarrierConfig app. This string is used only if - * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true + * String to identify carrier name in CarrierConfig app. This string overrides SPN if + * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true; otherwise, it will be used if its value is provided + * and SPN is unavailable * @hide */ public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f411ef71c94b..8edc8b17c9e0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -53,6 +53,7 @@ import android.util.Log; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; @@ -2514,6 +2515,33 @@ public class TelephonyManager { } } + /** + * Resets the Carrier Keys in the database. This involves 2 steps: + * 1. Delete the keys from the database. + * 2. Send an intent to download new Certificates. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * @hide + */ + public void resetCarrierKeysForImsiEncryption() { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) { + throw new RuntimeException("IMSI error: Subscriber Info is null"); + } + int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId()); + info.resetCarrierKeysForImsiEncryption(subId, mContext.getOpPackageName()); + } catch (RemoteException ex) { + Rlog.e(TAG, "getCarrierInfoForImsiEncryption RemoteException" + ex); + throw new RuntimeException("IMSI error: Remote Exception"); + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + Rlog.e(TAG, "getCarrierInfoForImsiEncryption NullPointerException" + ex); + throw new RuntimeException("IMSI error: Null Pointer exception"); + } + } + /** * @param keyAvailability bitmask that defines the availabilty of keys for a type. * @param keyType the key type which is being checked. (WLAN, EPDG) @@ -2549,7 +2577,7 @@ public class TelephonyManager { * device keystore. * <p> * Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} * @param imsiEncryptionInfo which includes the Key Type, the Public Key * (java.security.PublicKey) and the Key Identifier.and the Key Identifier. * The keyIdentifier Attribute value pair that helps a server locate @@ -4932,6 +4960,25 @@ public class TelephonyManager { } /** + * @return the {@IImsRegistration} interface that corresponds with the slot index and feature. + * @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for. + * @param feature An integer indicating the feature that we wish to get the ImsRegistration for. + * Corresponds to features defined in ImsFeature. + * @hide + */ + public @Nullable IImsRegistration getImsRegistration(int slotIndex, int feature) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getImsRegistration(slotIndex, feature); + } + } catch (RemoteException e) { + Rlog.e(TAG, "getImsRegistration, RemoteException: " + e.getMessage()); + } + return null; + } + + /** * Set IMS registration state * * @param Registration state diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index 8230eafc2e4d..aaa0f08594d1 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -26,12 +26,14 @@ import android.telephony.CarrierConfigManager; import android.telephony.ims.feature.ImsFeature; import android.telephony.ims.feature.MMTelFeature; import android.telephony.ims.feature.RcsFeature; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import android.util.SparseArray; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceController; import com.android.internal.annotations.VisibleForTesting; @@ -113,6 +115,12 @@ public class ImsService extends Service { throws RemoteException { ImsService.this.removeImsFeature(slotId, featureType, c); } + + @Override + public IImsRegistration getRegistration(int slotId) throws RemoteException { + ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); + return r != null ? r.getBinder() : null; + } }; /** @@ -174,6 +182,8 @@ public class ImsService extends Service { f.setSlotId(slotId); f.addImsFeatureStatusCallback(c); addImsFeature(slotId, featureType, f); + // TODO: Remove once new onFeatureReady AIDL is merged in. + f.onFeatureReady(); } private void addImsFeature(int slotId, int featureType, ImsFeature f) { @@ -236,4 +246,13 @@ public class ImsService extends Service { public @Nullable RcsFeature onCreateRcsFeature(int slotId) { return null; } + + /** + * @param slotId The slot that is associated with the IMS Registration. + * @return the ImsRegistration implementation associated with the slot. + * @hide + */ + public ImsRegistrationImplBase getRegistration(int slotId) { + return new ImsRegistrationImplBase(); + } } diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index ca4a210e30cc..d47cea3097f3 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -96,7 +96,7 @@ public abstract class ImsFeature { new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); private @ImsState int mState = STATE_NOT_AVAILABLE; private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - private Context mContext; + protected Context mContext; public void setContext(Context context) { mContext = context; diff --git a/telephony/java/android/telephony/ims/internal/ImsService.java b/telephony/java/android/telephony/ims/internal/ImsService.java index b7c8ca0f9799..afaf33294d8a 100644 --- a/telephony/java/android/telephony/ims/internal/ImsService.java +++ b/telephony/java/android/telephony/ims/internal/ImsService.java @@ -24,7 +24,6 @@ import android.telephony.CarrierConfigManager; import android.telephony.ims.internal.aidl.IImsConfig; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsRegistration; import android.telephony.ims.internal.aidl.IImsServiceController; import android.telephony.ims.internal.aidl.IImsServiceControllerListener; import android.telephony.ims.internal.feature.ImsFeature; @@ -32,11 +31,12 @@ import android.telephony.ims.internal.feature.MmTelFeature; import android.telephony.ims.internal.feature.RcsFeature; import android.telephony.ims.internal.stub.ImsConfigImplBase; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import android.util.SparseArray; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsRegistration; import com.android.internal.annotations.VisibleForTesting; /** diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl index 8332bc024e37..43f5098af3ca 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl @@ -24,4 +24,5 @@ import com.android.ims.internal.IImsCallSession; */ oneway interface IImsMmTelListener { void onIncomingCall(IImsCallSession c); + void onVoiceMessageCountUpdate(int count); }
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl index 8afb95588b01..82a85254bbca 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl @@ -18,12 +18,12 @@ package android.telephony.ims.internal.aidl; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsRegistration; import android.telephony.ims.internal.aidl.IImsConfig; import android.telephony.ims.internal.aidl.IImsServiceControllerListener; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsRegistration; /** * See ImsService and MmTelFeature for more information. diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java index 4d188734c10e..5dbf077ee7c5 100644 --- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java @@ -18,7 +18,7 @@ package android.telephony.ims.internal.feature; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.ArraySet; import java.util.ArrayList; diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java index 238411ee4f8f..8d888c2bcb28 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java @@ -28,8 +28,8 @@ import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.stub.ImsRegistrationImplBase; import android.telephony.ims.internal.aidl.IImsSmsListener; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; import android.telephony.ims.stub.ImsUtImplBase; @@ -262,6 +262,15 @@ public class MmTelFeature extends ImsFeature { } /** + * Updates the Listener when the voice message count for IMS has changed. + * @param count an integer representing the new message count. + */ + @Override + public void onVoiceMessageCountUpdate(int count) { + + } + + /** * Called when the IMS provider receives an incoming call. * @param c The {@link ImsCallSession} associated with the new call. */ diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 558b009ab4c2..42af08365f61 100644 --- a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -14,16 +14,19 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; import android.annotation.IntDef; +import android.net.Uri; +import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.telephony.ims.internal.aidl.IImsRegistration; -import android.telephony.ims.internal.aidl.IImsRegistrationCallback; import android.util.Log; import com.android.ims.ImsReasonInfo; +import com.android.ims.internal.IImsRegistration; +import com.android.ims.internal.IImsRegistrationCallback; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -62,23 +65,25 @@ public class ImsRegistrationImplBase { // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current // state. + // The unknown state is set as the initialization state. This is so that we do not call back + // with NOT_REGISTERED in the case where the ImsService has not updated the registration state + // yet. + private static final int REGISTRATION_STATE_UNKNOWN = -1; private static final int REGISTRATION_STATE_NOT_REGISTERED = 0; private static final int REGISTRATION_STATE_REGISTERING = 1; private static final int REGISTRATION_STATE_REGISTERED = 2; - /** * Callback class for receiving Registration callback events. + * @hide */ - public static class Callback extends IImsRegistrationCallback.Stub { - + public static class Callback { /** * Notifies the framework when the IMS Provider is connected to the IMS network. * * @param imsRadioTech the radio access technology. Valid values are defined in * {@link ImsRegistrationTech}. */ - @Override public void onRegistered(@ImsRegistrationTech int imsRadioTech) { } @@ -88,7 +93,6 @@ public class ImsRegistrationImplBase { * @param imsRadioTech the radio access technology. Valid values are defined in * {@link ImsRegistrationTech}. */ - @Override public void onRegistering(@ImsRegistrationTech int imsRadioTech) { } @@ -97,7 +101,6 @@ public class ImsRegistrationImplBase { * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. */ - @Override public void onDeregistered(ImsReasonInfo info) { } @@ -108,10 +111,19 @@ public class ImsRegistrationImplBase { * @param imsRadioTech The {@link ImsRegistrationTech} type that has failed * @param info A {@link ImsReasonInfo} that identifies the reason for failure. */ - @Override public void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { } + + /** + * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when + * it changes. + * @param uris new array of subscriber {@link Uri}s that are associated with this IMS + * subscription. + */ + public void onSubscriberAssociatedUriChanged(Uri[] uris) { + + } } private final IImsRegistration mBinder = new IImsRegistration.Stub() { @@ -139,9 +151,9 @@ public class ImsRegistrationImplBase { private @ImsRegistrationTech int mConnectionType = REGISTRATION_TECH_NONE; // Locked on mLock - private int mRegistrationState = REGISTRATION_STATE_NOT_REGISTERED; - // Locked on mLock - private ImsReasonInfo mLastDisconnectCause; + private int mRegistrationState = REGISTRATION_STATE_UNKNOWN; + // Locked on mLock, create unspecified disconnect cause. + private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo(); public final IImsRegistration getBinder() { return mBinder; @@ -221,6 +233,17 @@ public class ImsRegistrationImplBase { }); } + public final void onSubscriberAssociatedUriChanged(Uri[] uris) { + mCallbacks.broadcast((c) -> { + try { + c.onSubscriberAssociatedUriChanged(uris); + } catch (RemoteException e) { + Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping " + + "callback."); + } + }); + } + private void updateToState(@ImsRegistrationTech int connType, int newState) { synchronized (mLock) { mConnectionType = connType; @@ -241,7 +264,8 @@ public class ImsRegistrationImplBase { } } - private @ImsRegistrationTech int getConnectionType() { + @VisibleForTesting + public final @ImsRegistrationTech int getConnectionType() { synchronized (mLock) { return mConnectionType; } @@ -271,6 +295,10 @@ public class ImsRegistrationImplBase { c.onRegistered(getConnectionType()); break; } + case REGISTRATION_STATE_UNKNOWN: { + // Do not callback if the state has not been updated yet by the ImsService. + break; + } } } } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl b/telephony/java/com/android/ims/internal/IImsRegistration.aidl index 687b7ca408d5..6de264ec90fb 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistration.aidl @@ -15,10 +15,9 @@ */ -package android.telephony.ims.internal.aidl; +package com.android.ims.internal; -import android.telephony.ims.internal.aidl.IImsRegistrationCallback; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import com.android.ims.internal.IImsRegistrationCallback; /** * See ImsRegistration for more information. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl index a50575b96865..5f21167422dc 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl @@ -15,8 +15,9 @@ */ -package android.telephony.ims.internal.aidl; +package com.android.ims.internal; +import android.net.Uri; import android.telephony.ims.internal.stub.ImsFeatureConfiguration; import com.android.ims.ImsReasonInfo; @@ -31,4 +32,5 @@ oneway interface IImsRegistrationCallback { void onRegistering(int imsRadioTech); void onDeregistered(in ImsReasonInfo info); void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info); + void onSubscriberAssociatedUriChanged(in Uri[] uris); }
\ No newline at end of file diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl index 857089fac33a..7ac25ac13fbe 100644 --- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl +++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl @@ -18,6 +18,7 @@ package com.android.ims.internal; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsRcsFeature; /** @@ -29,4 +30,5 @@ interface IImsServiceController { IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c); IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c); void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c); + IImsRegistration getRegistration(int slotId); } diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl index 0f3182136997..f8a040da30ba 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl @@ -152,6 +152,13 @@ interface IPhoneSubInfo { in ImsiEncryptionInfo imsiEncryptionInfo); /** + * Resets the Carrier Keys in the database. This involves 2 steps: + * 1. Delete the keys from the database. + * 2. Send an intent to download new Certificates. + */ + void resetCarrierKeysForImsiEncryption(int subId, String callingPackage); + + /** * Retrieves the alpha identifier associated with the voice mail number. */ String getVoiceMailAlphaTag(String callingPackage); diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 416146fcb98e..fba82ee17f77 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -40,6 +40,7 @@ import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.OperatorInfo; @@ -808,6 +809,11 @@ interface ITelephony { IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback); /** + * Returns the IImsRegistration associated with the slot and feature specified. + */ + IImsRegistration getImsRegistration(int slotId, int feature); + + /** * Set the network selection mode to automatic. * * @param subId the id of the subscription to update. diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java index f29d993c55da..51369d06bf46 100644 --- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java +++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java @@ -486,4 +486,10 @@ public class TelephonyIntents { */ public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE"; + + /** + * Broadcast action to trigger the Carrier Certificate download. + */ + public static final String ACTION_CARRIER_CERTIFICATE_DOWNLOAD = + "com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD"; } diff --git a/test-base/Android.bp b/test-base/Android.bp index d2da613c3832..ccf57b00a379 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -49,7 +49,8 @@ java_library { // Build the repackaged.android.test.base library // ============================================== -// This contains repackaged versions of the classes from legacy-test. +// This contains repackaged versions of the classes from +// android.test.base. java_library_static { name: "repackaged.android.test.base", diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 8eddec48611b..b1ae40e17b9d 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -24,7 +24,6 @@ java_library { no_framework_libs: true, libs: [ "framework", - "legacy-test", ], } diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 85844a0cff81..dfaeed5e271e 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -24,7 +24,7 @@ java_library { no_framework_libs: true, libs: [ "framework", - "legacy-test", + "android.test.base", "android.test.mock", ], } @@ -40,7 +40,7 @@ java_library { no_framework_libs: true, libs: [ "framework", - "legacy-test", + "android.test.base", "android.test.mock", "junit", ], diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java index efc01f2ace6f..f6c5532363e8 100644 --- a/tests/net/java/android/net/IpSecConfigTest.java +++ b/tests/net/java/android/net/IpSecConfigTest.java @@ -36,19 +36,16 @@ public class IpSecConfigTest { public void testDefaults() throws Exception { IpSecConfig c = new IpSecConfig(); assertEquals(IpSecTransform.MODE_TRANSPORT, c.getMode()); - assertEquals("", c.getLocalAddress()); - assertEquals("", c.getRemoteAddress()); + assertEquals("", c.getSourceAddress()); + assertEquals("", c.getDestinationAddress()); assertNull(c.getNetwork()); assertEquals(IpSecTransform.ENCAP_NONE, c.getEncapType()); assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getEncapSocketResourceId()); assertEquals(0, c.getEncapRemotePort()); assertEquals(0, c.getNattKeepaliveInterval()); - for (int direction : - new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}) { - assertNull(c.getEncryption(direction)); - assertNull(c.getAuthentication(direction)); - assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId(direction)); - } + assertNull(c.getEncryption()); + assertNull(c.getAuthentication()); + assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId()); } @Test @@ -57,34 +54,21 @@ public class IpSecConfigTest { IpSecConfig c = new IpSecConfig(); c.setMode(IpSecTransform.MODE_TUNNEL); - c.setLocalAddress("0.0.0.0"); - c.setRemoteAddress("1.2.3.4"); + c.setSourceAddress("0.0.0.0"); + c.setDestinationAddress("1.2.3.4"); c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP); c.setEncapSocketResourceId(7); c.setEncapRemotePort(22); c.setNattKeepaliveInterval(42); c.setEncryption( - IpSecTransform.DIRECTION_OUT, new IpSecAlgorithm( IpSecAlgorithm.CRYPT_AES_CBC, new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF})); c.setAuthentication( - IpSecTransform.DIRECTION_OUT, new IpSecAlgorithm( IpSecAlgorithm.AUTH_HMAC_MD5, new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0})); - c.setSpiResourceId(IpSecTransform.DIRECTION_OUT, 1984); - c.setEncryption( - IpSecTransform.DIRECTION_IN, - new IpSecAlgorithm( - IpSecAlgorithm.CRYPT_AES_CBC, - new byte[] {2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF})); - c.setAuthentication( - IpSecTransform.DIRECTION_IN, - new IpSecAlgorithm( - IpSecAlgorithm.AUTH_HMAC_MD5, - new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 1})); - c.setSpiResourceId(IpSecTransform.DIRECTION_IN, 99); + c.setSpiResourceId(1984); assertParcelingIsLossless(c); } diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java index 0f40b4562b0d..cc3366fbc832 100644 --- a/tests/net/java/android/net/IpSecManagerTest.java +++ b/tests/net/java/android/net/IpSecManagerTest.java @@ -81,15 +81,13 @@ public class IpSecManagerTest { IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI); when(mMockIpSecService.allocateSecurityParameterIndex( - eq(IpSecTransform.DIRECTION_IN), eq(GOOGLE_DNS_4.getHostAddress()), eq(DROID_SPI), anyObject())) .thenReturn(spiResp); IpSecManager.SecurityParameterIndex droidSpi = - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_IN, GOOGLE_DNS_4, DROID_SPI); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, DROID_SPI); assertEquals(DROID_SPI, droidSpi.getSpi()); droidSpi.close(); @@ -103,15 +101,13 @@ public class IpSecManagerTest { IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.OK, resourceId, DROID_SPI); when(mMockIpSecService.allocateSecurityParameterIndex( - eq(IpSecTransform.DIRECTION_OUT), eq(GOOGLE_DNS_4.getHostAddress()), eq(IpSecManager.INVALID_SECURITY_PARAMETER_INDEX), anyObject())) .thenReturn(spiResp); IpSecManager.SecurityParameterIndex randomSpi = - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); assertEquals(DROID_SPI, randomSpi.getSpi()); @@ -124,16 +120,15 @@ public class IpSecManagerTest { * Throws resource unavailable exception */ @Test - public void testAllocSpiResUnavaiableExeption() throws Exception { + public void testAllocSpiResUnavailableException() throws Exception { IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE, 0, 0); when(mMockIpSecService.allocateSecurityParameterIndex( - anyInt(), anyString(), anyInt(), anyObject())) + anyString(), anyInt(), anyObject())) .thenReturn(spiResp); try { - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); fail("ResourceUnavailableException was not thrown"); } catch (IpSecManager.ResourceUnavailableException e) { } @@ -143,15 +138,14 @@ public class IpSecManagerTest { * Throws spi unavailable exception */ @Test - public void testAllocSpiSpiUnavaiableExeption() throws Exception { + public void testAllocSpiSpiUnavailableException() throws Exception { IpSecSpiResponse spiResp = new IpSecSpiResponse(IpSecManager.Status.SPI_UNAVAILABLE, 0, 0); when(mMockIpSecService.allocateSecurityParameterIndex( - anyInt(), anyString(), anyInt(), anyObject())) + anyString(), anyInt(), anyObject())) .thenReturn(spiResp); try { - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4); fail("ResourceUnavailableException was not thrown"); } catch (IpSecManager.ResourceUnavailableException e) { } @@ -163,8 +157,7 @@ public class IpSecManagerTest { @Test public void testRequestAllocInvalidSpi() throws Exception { try { - mIpSecManager.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, GOOGLE_DNS_4, 0); + mIpSecManager.allocateSecurityParameterIndex(GOOGLE_DNS_4, 0); fail("Able to allocate invalid spi"); } catch (IllegalArgumentException e) { } diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java index 473dc538f09d..9aad413c354b 100644 --- a/tests/net/java/android/net/MacAddressTest.java +++ b/tests/net/java/android/net/MacAddressTest.java @@ -67,7 +67,7 @@ public class MacAddressTest { assertEquals(msg, t.expectedType, got); if (got != MacAddress.TYPE_UNKNOWN) { - assertEquals(got, MacAddress.fromBytes(t.addr).addressType()); + assertEquals(got, MacAddress.fromBytes(t.addr).getAddressType()); } } } @@ -191,7 +191,7 @@ public class MacAddressTest { assertTrue(stringRepr + " expected to be a locally assigned address", mac.isLocallyAssigned()); - assertEquals(MacAddress.TYPE_UNICAST, mac.addressType()); + assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType()); assertTrue(stringRepr + " expected to begin with " + expectedLocalOui, stringRepr.startsWith(expectedLocalOui)); } diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/java/android/net/NetworkTest.java index bacf986b3627..94d01e91d03b 100644 --- a/tests/net/java/android/net/NetworkTest.java +++ b/tests/net/java/android/net/NetworkTest.java @@ -147,9 +147,9 @@ public class NetworkTest { // Adjust as necessary to test an implementation's specific constants. // When running with runtest, "adb logcat -s TestRunner" can be useful. - assertEquals(4311403230L, one.getNetworkHandle()); - assertEquals(8606370526L, two.getNetworkHandle()); - assertEquals(12901337822L, three.getNetworkHandle()); + assertEquals(7700664333L, one.getNetworkHandle()); + assertEquals(11995631629L, two.getNetworkHandle()); + assertEquals(16290598925L, three.getNetworkHandle()); } private static <T> void assertNotEqual(T t1, T t2) { diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 2b0349c6fa83..b8e37f3a10ea 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1392,39 +1392,75 @@ public class ConnectivityServiceTest { return null; } - void expectAvailableCallbacks( - MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) { + // Expects onAvailable and the callbacks that follow it. These are: + // - onSuspended, iff the network was suspended when the callbacks fire. + // - onCapabilitiesChanged. + // - onLinkPropertiesChanged. + // + // @param agent the network to expect the callbacks on. + // @param expectSuspended whether to expect a SUSPENDED callback. + // @param expectValidated the expected value of the VALIDATED capability in the + // onCapabilitiesChanged callback. + // @param timeoutMs how long to wait for the callbacks. + void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, + boolean expectValidated, int timeoutMs) { expectCallback(CallbackState.AVAILABLE, agent, timeoutMs); if (expectSuspended) { expectCallback(CallbackState.SUSPENDED, agent, timeoutMs); } - expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs); + if (expectValidated) { + expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + } else { + expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent); + } expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs); } - void expectAvailableCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, TIMEOUT_MS); + // Expects the available callbacks (validated), plus onSuspended. + void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) { + expectAvailableCallbacks(agent, true, expectValidated, TIMEOUT_MS); + } + + void expectAvailableCallbacksValidated(MockNetworkAgent agent) { + expectAvailableCallbacks(agent, false, true, TIMEOUT_MS); + } + + void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) { + expectAvailableCallbacks(agent, false, false, TIMEOUT_MS); } - void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, true, TIMEOUT_MS); + // Expects the available callbacks (where the onCapabilitiesChanged must contain the + // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the + // one we just sent. + // TODO: this is likely a bug. Fix it and remove this method. + void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) { + expectCallback(CallbackState.AVAILABLE, agent, TIMEOUT_MS); + NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + expectCallback(CallbackState.LINK_PROPERTIES, agent, TIMEOUT_MS); + NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); + assertEquals(nc1, nc2); } - void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, TIMEOUT_MS); + // Expects the available callbacks where the onCapabilitiesChanged must not have validated, + // then expects another onCapabilitiesChanged that has the validated bit set. This is used + // when a network connects and satisfies a callback, and then immediately validates. + void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) { + expectAvailableCallbacksUnvalidated(agent); expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); } - void expectCapabilitiesWith(int capability, MockNetworkAgent agent) { + NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) { CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent); NetworkCapabilities nc = (NetworkCapabilities) cbi.arg; assertTrue(nc.hasCapability(capability)); + return nc; } - void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) { + NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) { CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent); NetworkCapabilities nc = (NetworkCapabilities) cbi.arg; assertFalse(nc.hasCapability(capability)); + return nc; } void assertNoCallback() { @@ -1461,8 +1497,8 @@ public class ConnectivityServiceTest { ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); - cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1476,8 +1512,8 @@ public class ConnectivityServiceTest { cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1500,8 +1536,8 @@ public class ConnectivityServiceTest { // Test validated networks mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1513,10 +1549,10 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); @@ -1552,32 +1588,32 @@ public class ConnectivityServiceTest { mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.connect(true); // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request. // We then get LOSING when wifi validates and cell is outscored. - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mEthernetNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mEthernetNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mEthernetNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); for (int i = 0; i < 4; i++) { MockNetworkAgent oldNetwork, newNetwork; @@ -1594,7 +1630,7 @@ public class ConnectivityServiceTest { callback.expectCallback(CallbackState.LOSING, oldNetwork); // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no // longer lingering? - defaultCallback.expectAvailableCallbacks(newNetwork); + defaultCallback.expectAvailableCallbacksValidated(newNetwork); assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork()); } assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1614,7 +1650,7 @@ public class ConnectivityServiceTest { // Disconnect our test networks. mWiFiNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -1630,22 +1666,22 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); // Score: 10 - callback.expectAvailableCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi with a score of 20. // Cell stays up because it would satisfy the default request if it validated. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); // Score: 20 - callback.expectAvailableCallbacks(mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi with a score of 70. @@ -1653,33 +1689,33 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.adjustScore(50); mWiFiNetworkAgent.connect(false); // Score: 70 - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Tear down wifi. mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but // it's arguably correct to linger it, since it was the default network before it validated. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -1687,12 +1723,12 @@ public class ConnectivityServiceTest { // If a network is lingering, and we add and remove a request from it, resume lingering. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // TODO: Investigate sending validated before losing. callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); @@ -1711,7 +1747,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); // Cell is now the default network. Pin it with a cell-specific request. noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525 @@ -1720,8 +1756,8 @@ public class ConnectivityServiceTest { // Now connect wifi, and expect it to become the default network. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // The default request is lingering on cell, but nothing happens to cell, and we send no // callbacks for it, because it's kept up by cellRequest. callback.assertNoCallback(); @@ -1737,14 +1773,14 @@ public class ConnectivityServiceTest { // Register a TRACK_DEFAULT request and check that it does not affect lingering. TestNetworkCallback trackDefaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(trackDefaultCallback); - trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mEthernetNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); - trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); // Let linger run its course. callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs); @@ -1771,13 +1807,13 @@ public class ConnectivityServiceTest { // Bring up validated cell. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); // Bring up unvalidated wifi with explicitlySelected=true. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(false); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // Cell Remains the default. assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1800,7 +1836,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(false); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the // network to disconnect. @@ -1811,7 +1847,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.explicitlySelected(false); mWiFiNetworkAgent.connect(true); - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1820,7 +1856,7 @@ public class ConnectivityServiceTest { // TODO: fix this. mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET); mEthernetNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); callback.assertNoCallback(); @@ -1993,7 +2029,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); mCellNetworkAgent.connectWithoutInternet(); - networkCallback.expectAvailableCallbacks(mCellNetworkAgent); + networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS @@ -2022,7 +2058,7 @@ public class ConnectivityServiceTest { MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); - networkCallback.expectAvailableCallbacks(mmsNetworkAgent); + networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent @@ -2049,7 +2085,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); String firstRedirectUrl = "http://example.com/firstPath"; mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl); // Take down network. @@ -2062,7 +2098,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); String secondRedirectUrl = "http://example.com/secondPath"; mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl); // Make captive portal disappear then revalidate. @@ -2072,9 +2108,7 @@ public class ConnectivityServiceTest { captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - // TODO: Investigate only sending available callbacks. - validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. @@ -2098,7 +2132,7 @@ public class ConnectivityServiceTest { // Bring up wifi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Check that calling startCaptivePortalApp does nothing. @@ -2109,7 +2143,7 @@ public class ConnectivityServiceTest { // Turn into a captive portal. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302; mCm.reportNetworkConnectivity(wifiNetwork, false); - captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); // Check that startCaptivePortalApp sends the expected intent. @@ -2122,7 +2156,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); c.reportCaptivePortalDismissed(); - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); mCm.unregisterNetworkCallback(validatedCallback); @@ -2165,7 +2199,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); // But there should be no CaptivePortal callback. captivePortalCallback.assertNoCallback(); } @@ -2203,14 +2237,14 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - cEmpty1.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty2.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty3.expectAvailableCallbacks(mWiFiNetworkAgent); - cEmpty4.expectAvailableCallbacks(mWiFiNetworkAgent); + cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty3.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); + cEmpty4.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertNoCallbacks(cFoo, cBar); mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo")); - cFoo.expectAvailableCallbacks(mWiFiNetworkAgent); + cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent); } @@ -2219,7 +2253,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar")); cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - cBar.expectAvailableCallbacks(mWiFiNetworkAgent); + cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); for (TestNetworkCallback c: emptyCallbacks) { c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent); } @@ -2348,14 +2382,14 @@ public class ConnectivityServiceTest { // Bring up cell and expect CALLBACK_AVAILABLE. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); // Bring up wifi and expect CALLBACK_AVAILABLE. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); cellNetworkCallback.assertNoCallback(); - defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); + defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); // Bring down cell. Expect no default network callback, since it wasn't the default. mCellNetworkAgent.disconnect(); @@ -2365,7 +2399,7 @@ public class ConnectivityServiceTest { // Bring up cell. Expect no default network callback, since it won't be the default. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); defaultNetworkCallback.assertNoCallback(); // Bring down wifi. Expect the default network callback to notified of LOST wifi @@ -2373,7 +2407,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); cellNetworkCallback.assertNoCallback(); defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); mCellNetworkAgent.disconnect(); cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); @@ -2394,7 +2428,7 @@ public class ConnectivityServiceTest { // We should get onAvailable(), onCapabilitiesChanged(), and // onLinkPropertiesChanged() in rapid succession. Additionally, we // should get onCapabilitiesChanged() when the mobile network validates. - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); // Update LinkProperties. @@ -2415,7 +2449,7 @@ public class ConnectivityServiceTest { mCm.registerDefaultNetworkCallback(dfltNetworkCallback); // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(), // as well as onNetworkSuspended() in rapid succession. - dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent); + dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true); dfltNetworkCallback.assertNoCallback(); mCm.unregisterNetworkCallback(dfltNetworkCallback); @@ -2455,18 +2489,18 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); // When wifi connects, cell lingers. - callback.expectAvailableCallbacks(mWiFiNetworkAgent); + callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); - fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); @@ -2490,8 +2524,8 @@ public class ConnectivityServiceTest { // is currently delivered before the onAvailable() callbacks. // TODO: Fix this. cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); - cellCallback.expectAvailableCallbacks(mCellNetworkAgent); - fgCallback.expectAvailableCallbacks(mCellNetworkAgent); + cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); + fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); // Expect a network capabilities update with FOREGROUND, because the most recent // request causes its state to change. callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); @@ -2511,7 +2545,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); - fgCallback.expectAvailableCallbacks(mCellNetworkAgent); + fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertTrue(isForegroundNetwork(mCellNetworkAgent)); mCm.unregisterNetworkCallback(callback); @@ -2651,7 +2685,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); testFactory.expectAddRequests(2); // Because the cell request changes score twice. mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); testFactory.waitForNetworkRequests(2); assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us. @@ -2742,16 +2776,15 @@ public class ConnectivityServiceTest { // Bring up validated cell. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); - defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); Network cellNetwork = mCellNetworkAgent.getNetwork(); // Bring up validated wifi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi. @@ -2772,18 +2805,18 @@ public class ConnectivityServiceTest { // that we switch back to cell. tracker.configRestrictsAvoidBadWifi = false; tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(mCm.getActiveNetwork(), cellNetwork); // Switch back to a restrictive carrier. tracker.configRestrictsAvoidBadWifi = true; tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mCm.getActiveNetwork(), wifiNetwork); // Simulate the user selecting "switch" on the dialog, and check that we switch to cell. mCm.setAvoidUnvalidated(wifiNetwork); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( NET_CAPABILITY_VALIDATED)); assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( @@ -2794,9 +2827,8 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent.disconnect(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent); - validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Fail validation on wifi and expect the dialog to appear. @@ -2810,7 +2842,7 @@ public class ConnectivityServiceTest { tracker.reevaluate(); // We now switch to cell. - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( NET_CAPABILITY_VALIDATED)); assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( @@ -2821,17 +2853,17 @@ public class ConnectivityServiceTest { // We switch to wifi and then to cell. Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null); tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); assertEquals(mCm.getActiveNetwork(), wifiNetwork); Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1); tracker.reevaluate(); - defaultCallback.expectAvailableCallbacks(mCellNetworkAgent); + defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); assertEquals(mCm.getActiveNetwork(), cellNetwork); // If cell goes down, we switch to wifi. mCellNetworkAgent.disconnect(); defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); validatedWifiCallback.assertNoCallback(); mCm.unregisterNetworkCallback(cellNetworkCallback); @@ -2873,7 +2905,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, timeoutMs); // pass timeout and validate that UNAVAILABLE is not called networkCallback.assertNoCallback(); @@ -2894,7 +2926,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); final int assertTimeoutMs = 100; - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, assertTimeoutMs); mWiFiNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); @@ -3381,7 +3413,7 @@ public class ConnectivityServiceTest { // Bring up wifi aware network. wifiAware.connect(false, false); - callback.expectAvailableCallbacks(wifiAware); + callback.expectAvailableCallbacksUnvalidated(wifiAware); assertNull(mCm.getActiveNetworkInfo()); assertNull(mCm.getActiveNetwork()); diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index 2282c1319a9a..1ddab5b47846 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -32,7 +32,6 @@ import android.net.IpSecAlgorithm; import android.net.IpSecConfig; import android.net.IpSecManager; import android.net.IpSecSpiResponse; -import android.net.IpSecTransform; import android.net.IpSecTransformResponse; import android.net.NetworkUtils; import android.os.Binder; @@ -54,14 +53,14 @@ import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public class IpSecServiceParameterizedTest { - private static final int TEST_SPI_OUT = 0xD1201D; - private static final int TEST_SPI_IN = TEST_SPI_OUT + 1; + private static final int TEST_SPI = 0xD1201D; - private final String mRemoteAddr; + private final String mDestinationAddr; + private final String mSourceAddr; @Parameterized.Parameters public static Collection ipSecConfigs() { - return Arrays.asList(new Object[][] {{"8.8.4.4"}, {"2601::10"}}); + return Arrays.asList(new Object[][] {{"1.2.3.4", "8.8.4.4"}, {"2601::2", "2601::10"}}); } private static final byte[] AEAD_KEY = { @@ -96,11 +95,9 @@ public class IpSecServiceParameterizedTest { private static final IpSecAlgorithm AEAD_ALGO = new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); - private static final int[] DIRECTIONS = - new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT}; - - public IpSecServiceParameterizedTest(String remoteAddr) { - mRemoteAddr = remoteAddr; + public IpSecServiceParameterizedTest(String sourceAddr, String destAddr) { + mSourceAddr = sourceAddr; + mDestinationAddr = destAddr; } @Before @@ -116,44 +113,30 @@ public class IpSecServiceParameterizedTest { @Test public void testIpSecServiceReserveSpi() throws Exception { - when(mMockNetd.ipSecAllocateSpi( - anyInt(), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - eq(mRemoteAddr), - eq(TEST_SPI_OUT))) - .thenReturn(TEST_SPI_OUT); + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); IpSecSpiResponse spiResp = mIpSecService.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder()); + mDestinationAddr, TEST_SPI, new Binder()); assertEquals(IpSecManager.Status.OK, spiResp.status); - assertEquals(TEST_SPI_OUT, spiResp.spi); + assertEquals(TEST_SPI, spiResp.spi); } @Test public void testReleaseSecurityParameterIndex() throws Exception { - when(mMockNetd.ipSecAllocateSpi( - anyInt(), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - eq(mRemoteAddr), - eq(TEST_SPI_OUT))) - .thenReturn(TEST_SPI_OUT); + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); IpSecSpiResponse spiResp = mIpSecService.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder()); + mDestinationAddr, TEST_SPI, new Binder()); mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId); verify(mMockNetd) .ipSecDeleteSecurityAssociation( - eq(spiResp.resourceId), - anyInt(), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); + eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI)); // Verify quota and RefcountedResource objects cleaned up IpSecService.UserRecord userRecord = @@ -169,17 +152,12 @@ public class IpSecServiceParameterizedTest { @Test public void testSecurityParameterIndexBinderDeath() throws Exception { - when(mMockNetd.ipSecAllocateSpi( - anyInt(), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - eq(mRemoteAddr), - eq(TEST_SPI_OUT))) - .thenReturn(TEST_SPI_OUT); + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), eq(mDestinationAddr), eq(TEST_SPI))) + .thenReturn(TEST_SPI); IpSecSpiResponse spiResp = mIpSecService.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder()); + mDestinationAddr, TEST_SPI, new Binder()); IpSecService.UserRecord userRecord = mIpSecService.mUserResourceTracker.getUserRecord(Os.getuid()); @@ -190,11 +168,7 @@ public class IpSecServiceParameterizedTest { verify(mMockNetd) .ipSecDeleteSecurityAssociation( - eq(spiResp.resourceId), - anyInt(), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); + eq(spiResp.resourceId), anyString(), anyString(), eq(TEST_SPI)); // Verify quota and RefcountedResource objects cleaned up assertEquals(0, userRecord.mSpiQuotaTracker.mCurrent); @@ -206,14 +180,12 @@ public class IpSecServiceParameterizedTest { } } - private int getNewSpiResourceId(int direction, String remoteAddress, int returnSpi) - throws Exception { - when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt())) + private int getNewSpiResourceId(String remoteAddress, int returnSpi) throws Exception { + when(mMockNetd.ipSecAllocateSpi(anyInt(), anyString(), anyString(), anyInt())) .thenReturn(returnSpi); IpSecSpiResponse spi = mIpSecService.allocateSecurityParameterIndex( - direction, NetworkUtils.numericToInetAddress(remoteAddress).getHostAddress(), IpSecManager.INVALID_SECURITY_PARAMETER_INDEX, new Binder()); @@ -221,20 +193,14 @@ public class IpSecServiceParameterizedTest { } private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception { - config.setSpiResourceId( - IpSecTransform.DIRECTION_OUT, - getNewSpiResourceId(IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT)); - config.setSpiResourceId( - IpSecTransform.DIRECTION_IN, - getNewSpiResourceId(IpSecTransform.DIRECTION_IN, mRemoteAddr, TEST_SPI_IN)); - config.setRemoteAddress(mRemoteAddr); + config.setSpiResourceId(getNewSpiResourceId(mDestinationAddr, TEST_SPI)); + config.setSourceAddress(mSourceAddr); + config.setDestinationAddress(mDestinationAddr); } private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception { - for (int direction : DIRECTIONS) { - config.setEncryption(direction, CRYPT_ALGO); - config.setAuthentication(direction, AUTH_ALGO); - } + config.setEncryption(CRYPT_ALGO); + config.setAuthentication(AUTH_ALGO); } @Test @@ -251,32 +217,10 @@ public class IpSecServiceParameterizedTest { .ipSecAddSecurityAssociation( eq(createTransformResp.resourceId), anyInt(), - eq(IpSecTransform.DIRECTION_OUT), anyString(), anyString(), anyLong(), - eq(TEST_SPI_OUT), - eq(IpSecAlgorithm.AUTH_HMAC_SHA256), - eq(AUTH_KEY), - anyInt(), - eq(IpSecAlgorithm.CRYPT_AES_CBC), - eq(CRYPT_KEY), - anyInt(), - eq(""), - eq(new byte[] {}), - eq(0), - anyInt(), - anyInt(), - anyInt()); - verify(mMockNetd) - .ipSecAddSecurityAssociation( - eq(createTransformResp.resourceId), - anyInt(), - eq(IpSecTransform.DIRECTION_IN), - anyString(), - anyString(), - anyLong(), - eq(TEST_SPI_IN), + eq(TEST_SPI), eq(IpSecAlgorithm.AUTH_HMAC_SHA256), eq(AUTH_KEY), anyInt(), @@ -296,8 +240,7 @@ public class IpSecServiceParameterizedTest { IpSecConfig ipSecConfig = new IpSecConfig(); addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig); - ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_OUT, AEAD_ALGO); - ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO); + ipSecConfig.setAuthenticatedEncryption(AEAD_ALGO); IpSecTransformResponse createTransformResp = mIpSecService.createTransportModeTransform(ipSecConfig, new Binder()); @@ -307,32 +250,10 @@ public class IpSecServiceParameterizedTest { .ipSecAddSecurityAssociation( eq(createTransformResp.resourceId), anyInt(), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - anyString(), - anyLong(), - eq(TEST_SPI_OUT), - eq(""), - eq(new byte[] {}), - eq(0), - eq(""), - eq(new byte[] {}), - eq(0), - eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM), - eq(AEAD_KEY), - anyInt(), - anyInt(), - anyInt(), - anyInt()); - verify(mMockNetd) - .ipSecAddSecurityAssociation( - eq(createTransformResp.resourceId), - anyInt(), - eq(IpSecTransform.DIRECTION_IN), anyString(), anyString(), anyLong(), - eq(TEST_SPI_IN), + eq(TEST_SPI), eq(""), eq(new byte[] {}), eq(0), @@ -359,18 +280,7 @@ public class IpSecServiceParameterizedTest { verify(mMockNetd) .ipSecDeleteSecurityAssociation( - eq(createTransformResp.resourceId), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); - verify(mMockNetd) - .ipSecDeleteSecurityAssociation( - eq(createTransformResp.resourceId), - eq(IpSecTransform.DIRECTION_IN), - anyString(), - anyString(), - eq(TEST_SPI_IN)); + eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI)); // Verify quota and RefcountedResource objects cleaned up IpSecService.UserRecord userRecord = @@ -404,18 +314,7 @@ public class IpSecServiceParameterizedTest { verify(mMockNetd) .ipSecDeleteSecurityAssociation( - eq(createTransformResp.resourceId), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); - verify(mMockNetd) - .ipSecDeleteSecurityAssociation( - eq(createTransformResp.resourceId), - eq(IpSecTransform.DIRECTION_IN), - anyString(), - anyString(), - eq(TEST_SPI_IN)); + eq(createTransformResp.resourceId), anyString(), anyString(), eq(TEST_SPI)); // Verify quota and RefcountedResource objects cleaned up assertEquals(0, userRecord.mTransformQuotaTracker.mCurrent); @@ -439,30 +338,22 @@ public class IpSecServiceParameterizedTest { ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); int resourceId = createTransformResp.resourceId; - mIpSecService.applyTransportModeTransform(pfd, resourceId); + mIpSecService.applyTransportModeTransform(pfd, IpSecManager.DIRECTION_OUT, resourceId); verify(mMockNetd) .ipSecApplyTransportModeTransform( eq(pfd.getFileDescriptor()), eq(resourceId), - eq(IpSecTransform.DIRECTION_OUT), - anyString(), - anyString(), - eq(TEST_SPI_OUT)); - verify(mMockNetd) - .ipSecApplyTransportModeTransform( - eq(pfd.getFileDescriptor()), - eq(resourceId), - eq(IpSecTransform.DIRECTION_IN), + eq(IpSecManager.DIRECTION_OUT), anyString(), anyString(), - eq(TEST_SPI_IN)); + eq(TEST_SPI)); } @Test public void testRemoveTransportModeTransform() throws Exception { ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); - mIpSecService.removeTransportModeTransform(pfd, 1); + mIpSecService.removeTransportModeTransforms(pfd, 1); verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor()); } diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index 0467989d8984..b2a27e8c27b0 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -105,9 +105,6 @@ public class IpSecServiceTest { private static final IpSecAlgorithm AEAD_ALGO = new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, AEAD_KEY, 128); - private static final int[] DIRECTIONS = - new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT}; - static { try { INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0}); @@ -303,83 +300,75 @@ public class IpSecServiceTest { @Test public void testValidateAlgorithmsAuth() { - for (int direction : DIRECTIONS) { - // Validate that correct algorithm type succeeds - IpSecConfig config = new IpSecConfig(); - config.setAuthentication(direction, AUTH_ALGO); - mIpSecService.validateAlgorithms(config, direction); - - // Validate that incorrect algorithm types fails - for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) { - try { - config = new IpSecConfig(); - config.setAuthentication(direction, algo); - mIpSecService.validateAlgorithms(config, direction); - fail("Did not throw exception on invalid algorithm type"); - } catch (IllegalArgumentException expected) { - } + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthentication(AUTH_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {CRYPT_ALGO, AEAD_ALGO}) { + try { + config = new IpSecConfig(); + config.setAuthentication(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { } } } @Test public void testValidateAlgorithmsCrypt() { - for (int direction : DIRECTIONS) { - // Validate that correct algorithm type succeeds - IpSecConfig config = new IpSecConfig(); - config.setEncryption(direction, CRYPT_ALGO); - mIpSecService.validateAlgorithms(config, direction); - - // Validate that incorrect algorithm types fails - for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) { - try { - config = new IpSecConfig(); - config.setEncryption(direction, algo); - mIpSecService.validateAlgorithms(config, direction); - fail("Did not throw exception on invalid algorithm type"); - } catch (IllegalArgumentException expected) { - } + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setEncryption(CRYPT_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, AEAD_ALGO}) { + try { + config = new IpSecConfig(); + config.setEncryption(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { } } } @Test public void testValidateAlgorithmsAead() { - for (int direction : DIRECTIONS) { - // Validate that correct algorithm type succeeds - IpSecConfig config = new IpSecConfig(); - config.setAuthenticatedEncryption(direction, AEAD_ALGO); - mIpSecService.validateAlgorithms(config, direction); - - // Validate that incorrect algorithm types fails - for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) { - try { - config = new IpSecConfig(); - config.setAuthenticatedEncryption(direction, algo); - mIpSecService.validateAlgorithms(config, direction); - fail("Did not throw exception on invalid algorithm type"); - } catch (IllegalArgumentException expected) { - } + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthenticatedEncryption(AEAD_ALGO); + mIpSecService.validateAlgorithms(config); + + // Validate that incorrect algorithm types fails + for (IpSecAlgorithm algo : new IpSecAlgorithm[] {AUTH_ALGO, CRYPT_ALGO}) { + try { + config = new IpSecConfig(); + config.setAuthenticatedEncryption(algo); + mIpSecService.validateAlgorithms(config); + fail("Did not throw exception on invalid algorithm type"); + } catch (IllegalArgumentException expected) { } } } @Test public void testValidateAlgorithmsAuthCrypt() { - for (int direction : DIRECTIONS) { - // Validate that correct algorithm type succeeds - IpSecConfig config = new IpSecConfig(); - config.setAuthentication(direction, AUTH_ALGO); - config.setEncryption(direction, CRYPT_ALGO); - mIpSecService.validateAlgorithms(config, direction); - } + // Validate that correct algorithm type succeeds + IpSecConfig config = new IpSecConfig(); + config.setAuthentication(AUTH_ALGO); + config.setEncryption(CRYPT_ALGO); + mIpSecService.validateAlgorithms(config); } @Test public void testValidateAlgorithmsNoAlgorithms() { IpSecConfig config = new IpSecConfig(); try { - mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN); + mIpSecService.validateAlgorithms(config); fail("Expected exception; no algorithms specified"); } catch (IllegalArgumentException expected) { } @@ -388,10 +377,10 @@ public class IpSecServiceTest { @Test public void testValidateAlgorithmsAeadWithAuth() { IpSecConfig config = new IpSecConfig(); - config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO); - config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setAuthentication(AUTH_ALGO); try { - mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN); + mIpSecService.validateAlgorithms(config); fail("Expected exception; both AEAD and auth algorithm specified"); } catch (IllegalArgumentException expected) { } @@ -400,10 +389,10 @@ public class IpSecServiceTest { @Test public void testValidateAlgorithmsAeadWithCrypt() { IpSecConfig config = new IpSecConfig(); - config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO); - config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setEncryption(CRYPT_ALGO); try { - mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN); + mIpSecService.validateAlgorithms(config); fail("Expected exception; both AEAD and crypt algorithm specified"); } catch (IllegalArgumentException expected) { } @@ -412,11 +401,11 @@ public class IpSecServiceTest { @Test public void testValidateAlgorithmsAeadWithAuthAndCrypt() { IpSecConfig config = new IpSecConfig(); - config.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO); - config.setAuthentication(IpSecTransform.DIRECTION_IN, AUTH_ALGO); - config.setEncryption(IpSecTransform.DIRECTION_IN, CRYPT_ALGO); + config.setAuthenticatedEncryption(AEAD_ALGO); + config.setAuthentication(AUTH_ALGO); + config.setEncryption(CRYPT_ALGO); try { - mIpSecService.validateAlgorithms(config, IpSecTransform.DIRECTION_IN); + mIpSecService.validateAlgorithms(config); fail("Expected exception; AEAD, auth and crypt algorithm specified"); } catch (IllegalArgumentException expected) { } @@ -434,7 +423,7 @@ public class IpSecServiceTest { @Test public void testRemoveTransportModeTransform() throws Exception { ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket()); - mIpSecService.removeTransportModeTransform(pfd, 1); + mIpSecService.removeTransportModeTransforms(pfd, 1); verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor()); } @@ -447,7 +436,7 @@ public class IpSecServiceTest { try { IpSecSpiResponse spiResp = mIpSecService.allocateSecurityParameterIndex( - IpSecTransform.DIRECTION_OUT, address, DROID_SPI, new Binder()); + address, DROID_SPI, new Binder()); fail("Invalid address was passed through IpSecService validation: " + address); } catch (IllegalArgumentException e) { } catch (Exception e) { @@ -519,7 +508,6 @@ public class IpSecServiceTest { // tracks the resource ID. when(mMockNetd.ipSecAllocateSpi( anyInt(), - eq(IpSecTransform.DIRECTION_OUT), anyString(), eq(InetAddress.getLoopbackAddress().getHostAddress()), anyInt())) @@ -528,7 +516,6 @@ public class IpSecServiceTest { for (int i = 0; i < MAX_NUM_SPIS; i++) { IpSecSpiResponse newSpi = mIpSecService.allocateSecurityParameterIndex( - 0x1, InetAddress.getLoopbackAddress().getHostAddress(), DROID_SPI + i, new Binder()); @@ -544,7 +531,6 @@ public class IpSecServiceTest { // Try to reserve one more SPI, and should fail. IpSecSpiResponse extraSpi = mIpSecService.allocateSecurityParameterIndex( - 0x1, InetAddress.getLoopbackAddress().getHostAddress(), DROID_SPI + MAX_NUM_SPIS, new Binder()); @@ -558,7 +544,6 @@ public class IpSecServiceTest { // Should successfully reserve one more spi. extraSpi = mIpSecService.allocateSecurityParameterIndex( - 0x1, InetAddress.getLoopbackAddress().getHostAddress(), DROID_SPI + MAX_NUM_SPIS, new Binder()); diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp index 80853b13a7cc..0e57f7ff0ed2 100644 --- a/tools/stats_log_api_gen/Collation.cpp +++ b/tools/stats_log_api_gen/Collation.cpp @@ -15,6 +15,7 @@ */ #include "Collation.h" +#include "frameworks/base/cmds/statsd/src/atoms.pb.h" #include <stdio.h> #include <map> @@ -137,6 +138,16 @@ java_type(const FieldDescriptor* field) } /** + * Gather the enums info. + */ +void collate_enums(const EnumDescriptor &enumDescriptor, AtomField *atomField) { + for (int i = 0; i < enumDescriptor.value_count(); i++) { + atomField->enumValues[enumDescriptor.value(i)->number()] = + enumDescriptor.value(i)->name().c_str(); + } +} + +/** * Gather the info about an atom proto. */ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, @@ -221,11 +232,7 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, if (javaType == JAVA_TYPE_ENUM) { // All enums are treated as ints when it comes to function signatures. signature->push_back(JAVA_TYPE_INT); - const EnumDescriptor *enumDescriptor = field->enum_type(); - for (int i = 0; i < enumDescriptor->value_count(); i++) { - atField.enumValues[enumDescriptor->value(i)->number()] = - enumDescriptor->value(i)->name().c_str(); - } + collate_enums(*field->enum_type(), &atField); } else { signature->push_back(javaType); } @@ -235,6 +242,53 @@ int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, return errorCount; } +// This function flattens the fields of the AttributionNode proto in an Atom proto and generates +// the corresponding atom decl and signature. +bool get_non_chained_node(const Descriptor *atom, AtomDecl *atomDecl, + vector<java_type_t> *signature) { + // Build a sorted list of the fields. Descriptor has them in source file + // order. + map<int, const FieldDescriptor *> fields; + for (int j = 0; j < atom->field_count(); j++) { + const FieldDescriptor *field = atom->field(j); + fields[field->number()] = field; + } + + AtomDecl attributionDecl; + vector<java_type_t> attributionSignature; + collate_atom(android::os::statsd::AttributionNode::descriptor(), + &attributionDecl, &attributionSignature); + + // Build the type signature and the atom data. + bool has_attribution_node = false; + for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin(); + it != fields.end(); it++) { + const FieldDescriptor *field = it->second; + java_type_t javaType = java_type(field); + if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + atomDecl->fields.insert( + atomDecl->fields.end(), + attributionDecl.fields.begin(), attributionDecl.fields.end()); + signature->insert( + signature->end(), + attributionSignature.begin(), attributionSignature.end()); + has_attribution_node = true; + + } else { + AtomField atField(field->name(), javaType); + if (javaType == JAVA_TYPE_ENUM) { + // All enums are treated as ints when it comes to function signatures. + signature->push_back(JAVA_TYPE_INT); + collate_enums(*field->enum_type(), &atField); + } else { + signature->push_back(javaType); + } + atomDecl->fields.push_back(atField); + } + } + return has_attribution_node; +} + /** * Gather the info about the atoms. */ @@ -266,6 +320,13 @@ int collate_atoms(const Descriptor *descriptor, Atoms *atoms) { errorCount += collate_atom(atom, &atomDecl, &signature); atoms->signatures.insert(signature); atoms->decls.insert(atomDecl); + + AtomDecl nonChainedAtomDecl(atomField->number(), atomField->name(), atom->name()); + vector<java_type_t> nonChainedSignature; + if (get_non_chained_node(atom, &nonChainedAtomDecl, &nonChainedSignature)) { + atoms->non_chained_signatures.insert(nonChainedSignature); + atoms->non_chained_decls.insert(nonChainedAtomDecl); + } } if (dbg) { diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index cd0625c3a61d..0455eca62da8 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -32,6 +32,7 @@ using std::set; using std::string; using std::vector; using google::protobuf::Descriptor; +using google::protobuf::FieldDescriptor; /** * The types for atom parameters. @@ -93,14 +94,15 @@ struct AtomDecl { struct Atoms { set<vector<java_type_t>> signatures; set<AtomDecl> decls; + set<AtomDecl> non_chained_decls; + set<vector<java_type_t>> non_chained_signatures; }; /** * Gather the information about the atoms. Returns the number of errors. */ int collate_atoms(const Descriptor* descriptor, Atoms* atoms); -int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, - vector<java_type_t> *signature); +int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t> *signature); } // namespace stats_log_api_gen } // namespace android diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp index bbe6d63073c1..e0e6b5883e32 100644 --- a/tools/stats_log_api_gen/main.cpp +++ b/tools/stats_log_api_gen/main.cpp @@ -195,6 +195,47 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, fprintf(out, "\n"); } + for (set<vector<java_type_t>>::const_iterator signature = atoms.non_chained_signatures.begin(); + signature != atoms.non_chained_signatures.end(); signature++) { + int argIndex; + + fprintf(out, "void\n"); + fprintf(out, "stats_write_non_chained(int32_t code"); + argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + argIndex++; + } + fprintf(out, ")\n"); + + fprintf(out, "{\n"); + argIndex = 1; + fprintf(out, " android_log_event_list event(kStatsEventTag);\n"); + fprintf(out, " event << code;\n\n"); + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (argIndex == 1) { + fprintf(out, " event.begin();\n\n"); + fprintf(out, " event.begin();\n"); + } + if (*arg == JAVA_TYPE_STRING) { + fprintf(out, " if (arg%d == NULL) {\n", argIndex); + fprintf(out, " arg%d = \"\";\n", argIndex); + fprintf(out, " }\n"); + } + fprintf(out, " event << arg%d;\n", argIndex); + if (argIndex == 2) { + fprintf(out, " event.end();\n\n"); + fprintf(out, " event.end();\n\n"); + } + argIndex++; + } + + fprintf(out, " event.write(LOG_ID_STATS);\n"); + fprintf(out, "}\n"); + fprintf(out, "\n"); + } // Print footer fprintf(out, "\n"); fprintf(out, "} // namespace util\n"); @@ -203,6 +244,68 @@ static int write_stats_log_cpp(FILE *out, const Atoms &atoms, return 0; } +void build_non_chained_decl_map(const Atoms& atoms, + std::map<int, set<AtomDecl>::const_iterator>* decl_map){ + for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin(); + atom != atoms.non_chained_decls.end(); atom++) { + decl_map->insert(std::make_pair(atom->code, atom)); + } +} + +static void write_cpp_usage( + FILE* out, const string& method_name, const string& atom_code_name, + const AtomDecl& atom, const AtomDecl &attributionDecl) { + fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str()); + for (vector<AtomField>::const_iterator field = atom.fields.begin(); + field != atom.fields.end(); field++) { + if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + if (chainField.javaType == JAVA_TYPE_STRING) { + fprintf(out, ", const std::vector<%s>& %s", + cpp_type_name(chainField.javaType), + chainField.name.c_str()); + } else { + fprintf(out, ", const %s* %s, size_t %s_length", + cpp_type_name(chainField.javaType), + chainField.name.c_str(), chainField.name.c_str()); + } + } + } else { + fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); + } + } + fprintf(out, ");\n"); +} + +static void write_cpp_method_header( + FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures, + const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, "void %s(int32_t code ", method_name.c_str()); + int argIndex = 1; + for (vector<java_type_t>::const_iterator arg = signature->begin(); + arg != signature->end(); arg++) { + if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + if (chainField.javaType == JAVA_TYPE_STRING) { + fprintf(out, ", const std::vector<%s>& %s", + cpp_type_name(chainField.javaType), chainField.name.c_str()); + } else { + fprintf(out, ", const %s* %s, size_t %s_length", + cpp_type_name(chainField.javaType), + chainField.name.c_str(), chainField.name.c_str()); + } + } + } else { + fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + } + argIndex++; + } + fprintf(out, ");\n"); + + } +} static int write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) @@ -228,6 +331,9 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, " */\n"); fprintf(out, "enum {\n"); + std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map; + build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map); + size_t i = 0; // Print constants for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); @@ -236,26 +342,13 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, "\n"); fprintf(out, " /**\n"); fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); - fprintf(out, " * Usage: stats_write(StatsLog.%s", constant.c_str()); - for (vector<AtomField>::const_iterator field = atom->fields.begin(); - field != atom->fields.end(); field++) { - if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - if (chainField.javaType == JAVA_TYPE_STRING) { - fprintf(out, ", const std::vector<%s>& %s", - cpp_type_name(chainField.javaType), - chainField.name.c_str()); - } else { - fprintf(out, ", const %s* %s, size_t %s_length", - cpp_type_name(chainField.javaType), - chainField.name.c_str(), chainField.name.c_str()); - } - } - } else { - fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str()); - } + write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl); + + auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code); + if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) { + write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second, + attributionDecl); } - fprintf(out, ");\n"); fprintf(out, " */\n"); char const* const comma = (i == atoms.decls.size() - 1) ? "" : ","; fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma); @@ -274,38 +367,64 @@ write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributio fprintf(out, "//\n"); fprintf(out, "// Write methods\n"); fprintf(out, "//\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, "void stats_write(int32_t code "); + write_cpp_method_header(out, "stats_write", atoms.signatures, attributionDecl); + + fprintf(out, "//\n"); + fprintf(out, "// Write flattened methods\n"); + fprintf(out, "//\n"); + write_cpp_method_header(out, "stats_write_non_chained", atoms.non_chained_signatures, + attributionDecl); + + fprintf(out, "\n"); + fprintf(out, "} // namespace util\n"); + fprintf(out, "} // namespace android\n"); + + return 0; +} + +static void write_java_usage( + FILE* out, const string& method_name, const string& atom_code_name, + const AtomDecl& atom, const AtomDecl &attributionDecl) { + fprintf(out, " * Usage: StatsLog.%s(StatsLog.%s", + method_name.c_str(), atom_code_name.c_str()); + for (vector<AtomField>::const_iterator field = atom.fields.begin(); + field != atom.fields.end(); field++) { + if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { + for (auto chainField : attributionDecl.fields) { + fprintf(out, ", %s[] %s", + java_type_name(chainField.javaType), chainField.name.c_str()); + } + } else { + fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str()); + } + } + fprintf(out, ");\n"); +} + +static void write_java_method( + FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures, + const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, " public static native void %s(int code", method_name.c_str()); int argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { for (auto chainField : attributionDecl.fields) { - if (chainField.javaType == JAVA_TYPE_STRING) { - fprintf(out, ", const std::vector<%s>& %s", - cpp_type_name(chainField.javaType), chainField.name.c_str()); - } else { - fprintf(out, ", const %s* %s, size_t %s_length", - cpp_type_name(chainField.javaType), - chainField.name.c_str(), chainField.name.c_str()); - } + fprintf(out, ", %s[] %s", + java_type_name(chainField.javaType), chainField.name.c_str()); } } else { - fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex); + fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); } argIndex++; } fprintf(out, ");\n"); } - - fprintf(out, "\n"); - fprintf(out, "} // namespace util\n"); - fprintf(out, "} // namespace android\n"); - - return 0; } + static int write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) { @@ -322,6 +441,9 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD fprintf(out, "public class StatsLogInternal {\n"); fprintf(out, " // Constants for atom codes.\n"); + std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map; + build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map); + // Print constants for the atom codes. for (set<AtomDecl>::const_iterator atom = atoms.decls.begin(); atom != atoms.decls.end(); atom++) { @@ -329,19 +451,12 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD fprintf(out, "\n"); fprintf(out, " /**\n"); fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str()); - fprintf(out, " * Usage: StatsLog.write(StatsLog.%s", constant.c_str()); - for (vector<AtomField>::const_iterator field = atom->fields.begin(); - field != atom->fields.end(); field++) { - if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - fprintf(out, ", %s[] %s", - java_type_name(chainField.javaType), chainField.name.c_str()); - } - } else { - fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str()); - } + write_java_usage(out, "write", constant, *atom, attributionDecl); + auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code); + if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) { + write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second, + attributionDecl); } - fprintf(out, ");\n"); fprintf(out, " */\n"); fprintf(out, " public static final int %s = %d;\n", constant.c_str(), atom->code); } @@ -371,24 +486,8 @@ write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionD // Print write methods fprintf(out, " // Write methods\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, " public static native void write(int code"); - int argIndex = 1; - for (vector<java_type_t>::const_iterator arg = signature->begin(); - arg != signature->end(); arg++) { - if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { - for (auto chainField : attributionDecl.fields) { - fprintf(out, ", %s[] %s", - java_type_name(chainField.javaType), chainField.name.c_str()); - } - } else { - fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex); - } - argIndex++; - } - fprintf(out, ");\n"); - } + write_java_method(out, "write", atoms.signatures, attributionDecl); + write_java_method(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl); fprintf(out, "}\n"); @@ -431,9 +530,9 @@ jni_array_type_name(java_type_t type) } static string -jni_function_name(const vector<java_type_t>& signature) +jni_function_name(const string& method_name, const vector<java_type_t>& signature) { - string result("StatsLog_write"); + string result("StatsLog_" + method_name); for (vector<java_type_t>::const_iterator arg = signature.begin(); arg != signature.end(); arg++) { switch (*arg) { @@ -509,34 +608,17 @@ jni_function_signature(const vector<java_type_t>& signature, const AtomDecl &att } static int -write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) +write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp_method_name, + const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) { - // Print prelude - fprintf(out, "// This file is autogenerated\n"); - fprintf(out, "\n"); - - fprintf(out, "#include <statslog.h>\n"); - fprintf(out, "\n"); - fprintf(out, "#include <nativehelper/JNIHelp.h>\n"); - fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n"); - fprintf(out, "#include <utils/Vector.h>\n"); - fprintf(out, "#include \"core_jni_helpers.h\"\n"); - fprintf(out, "#include \"jni.h\"\n"); - fprintf(out, "\n"); - fprintf(out, "#define UNUSED __attribute__((__unused__))\n"); - fprintf(out, "\n"); - - fprintf(out, "namespace android {\n"); - fprintf(out, "\n"); - // Print write methods - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { int argIndex; fprintf(out, "static void\n"); fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code", - jni_function_name(*signature).c_str()); + jni_function_name(java_method_name, *signature).c_str()); argIndex = 1; for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { @@ -624,7 +706,7 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe // stats_write call argIndex = 1; - fprintf(out, " android::util::stats_write(code"); + fprintf(out, " android::util::%s(code", cpp_method_name.c_str()); for (vector<java_type_t>::const_iterator arg = signature->begin(); arg != signature->end(); arg++) { if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) { @@ -675,17 +757,53 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe fprintf(out, "\n"); } + + return 0; +} + +void write_jni_registration(FILE* out, const string& java_method_name, + const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) { + for (set<vector<java_type_t>>::const_iterator signature = signatures.begin(); + signature != signatures.end(); signature++) { + fprintf(out, " { \"%s\", \"%s\", (void*)%s },\n", + java_method_name.c_str(), + jni_function_signature(*signature, attributionDecl).c_str(), + jni_function_name(java_method_name, *signature).c_str()); + } +} + +static int +write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl) +{ + // Print prelude + fprintf(out, "// This file is autogenerated\n"); + fprintf(out, "\n"); + + fprintf(out, "#include <statslog.h>\n"); + fprintf(out, "\n"); + fprintf(out, "#include <nativehelper/JNIHelp.h>\n"); + fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n"); + fprintf(out, "#include <utils/Vector.h>\n"); + fprintf(out, "#include \"core_jni_helpers.h\"\n"); + fprintf(out, "#include \"jni.h\"\n"); + fprintf(out, "\n"); + fprintf(out, "#define UNUSED __attribute__((__unused__))\n"); + fprintf(out, "\n"); + + fprintf(out, "namespace android {\n"); + fprintf(out, "\n"); + + write_stats_log_jni(out, "write", "stats_write", atoms.signatures, attributionDecl); + write_stats_log_jni(out, "write_non_chained", "stats_write_non_chained", + atoms.non_chained_signatures, attributionDecl); + // Print registration function table fprintf(out, "/*\n"); fprintf(out, " * JNI registration.\n"); fprintf(out, " */\n"); fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n"); - for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin(); - signature != atoms.signatures.end(); signature++) { - fprintf(out, " { \"write\", \"%s\", (void*)%s },\n", - jni_function_signature(*signature, attributionDecl).c_str(), - jni_function_name(*signature).c_str()); - } + write_jni_registration(out, "write", atoms.signatures, attributionDecl); + write_jni_registration(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl); fprintf(out, "};\n"); fprintf(out, "\n"); @@ -699,11 +817,9 @@ write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDe fprintf(out, "\n"); fprintf(out, "} // namespace android\n"); - return 0; } - static void print_usage() { |