diff options
113 files changed, 5722 insertions, 1678 deletions
diff --git a/Android.mk b/Android.mk index 2b12416f8b4e..4e036b598027 100644 --- a/Android.mk +++ b/Android.mk @@ -906,8 +906,7 @@ endef # FRAMEWORKS_BASE_SUBDIRS comes from build/core/pathmap.mk dirs_to_document := \ $(dirs_to_check_apis) \ - $(addprefix ../../, $(FRAMEWORKS_DATA_BINDING_JAVA_SRC_DIRS)) \ - $(addprefix ../../, $(FRAMEWORKS_SUPPORT_JAVA_SRC_DIRS)) \ + $(addprefix ../../, $(FRAMEWORKS_DATA_BINDING_JAVA_SRC_DIRS)) patterns_to_not_document := \ $(call find-no-docs-pattern, $(dirs_to_document)) @@ -942,7 +941,8 @@ framework_docs_LOCAL_DROIDDOC_SOURCE_PATH := \ framework_docs_LOCAL_INTERMEDIATE_SOURCES := \ $(framework_res_source_path)/android/R.java \ $(framework_res_source_path)/android/Manifest.java \ - $(framework_res_source_path)/com/android/internal/R.java + $(framework_res_source_path)/com/android/internal/R.java \ + $(patsubst $(TARGET_OUT_COMMON_INTERMEDIATES)/%,%,$(libcore_to_document_generated)) framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \ core-oj \ @@ -955,6 +955,8 @@ framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \ framework \ voip-common +# Platform docs can refer to Support Library APIs, but we don't actually build +# them as part of the docs target, so we need to include them on the classpath. framework_docs_LOCAL_JAVA_LIBRARIES := \ $(framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES) \ $(FRAMEWORKS_SUPPORT_JAVA_LIBRARIES) @@ -1001,20 +1003,15 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS := \ -werror -hide 111 -hide 113 -hide 121 \ -overview $(LOCAL_PATH)/core/java/overview.html \ -# Allow the support library to add its own droiddoc options. -include $(LOCAL_PATH)/../support/droiddoc.mk - framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR:= \ $(call intermediates-dir-for,JAVA_LIBRARIES,framework,,COMMON) framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:= \ - $(framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR) \ - $(foreach lib,$(FRAMEWORKS_SUPPORT_JAVA_LIBRARIES),$(call intermediates-dir-for,JAVA_LIBRARIES,$(lib),,COMMON)) \ - $(foreach lib,$(FRAMEWORKS_SUPPORT_JAVA_LIBRARIES),$(call intermediates-dir-for,JAVA_LIBRARIES,$(lib)-res,,COMMON)) + $(framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR) framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES := \ frameworks/base/docs/knowntags.txt \ - libcore/Docs.mk + $(libcore_to_document_generated) samples_dir := development/samples/browseable @@ -1053,6 +1050,11 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS += \ -resourcesdir $(LOCAL_PATH)/docs/html/reference/images/ \ -resourcesoutdir reference/android/images/ +# Federate Support Library references against local API file. +framework_docs_LOCAL_DROIDDOC_OPTIONS += \ + -federate SupportLib https://developer.android.com \ + -federationapi SupportLib prebuilts/sdk/current/support-api.txt + # ==== the api stubs and current.xml =========================== include $(CLEAR_VARS) diff --git a/api/current.txt b/api/current.txt index b520e589c177..d43dcb893ef2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -18040,13 +18040,11 @@ package android.icu.text { method public synchronized void applyLocalizedPattern(java.lang.String); method public synchronized void applyPattern(java.lang.String); method public synchronized boolean areSignificantDigitsUsed(); - method public synchronized boolean equals(java.lang.Object); method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition); - method public synchronized android.icu.util.Currency getCurrency(); method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo(); method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage(); method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols(); @@ -18054,12 +18052,8 @@ package android.icu.text { method public synchronized int getGroupingSize(); method public synchronized java.math.MathContext getMathContext(); method public synchronized android.icu.math.MathContext getMathContextICU(); - method public synchronized int getMaximumFractionDigits(); - method public synchronized int getMaximumIntegerDigits(); method public synchronized int getMaximumSignificantDigits(); method public synchronized byte getMinimumExponentDigits(); - method public synchronized int getMinimumFractionDigits(); - method public synchronized int getMinimumIntegerDigits(); method public synchronized int getMinimumSignificantDigits(); method public synchronized int getMultiplier(); method public synchronized java.lang.String getNegativePrefix(); @@ -18070,19 +18064,13 @@ package android.icu.text { method public synchronized java.lang.String getPositivePrefix(); method public synchronized java.lang.String getPositiveSuffix(); method public synchronized java.math.BigDecimal getRoundingIncrement(); - method public synchronized int getRoundingMode(); method public synchronized int getSecondaryGroupingSize(); - method public synchronized int hashCode(); method public synchronized boolean isDecimalPatternMatchRequired(); method public synchronized boolean isDecimalSeparatorAlwaysShown(); method public synchronized boolean isExponentSignAlwaysShown(); - method public synchronized boolean isGroupingUsed(); method public synchronized boolean isParseBigDecimal(); - method public synchronized boolean isParseIntegerOnly(); - method public synchronized boolean isParseStrict(); method public synchronized boolean isScientificNotation(); method public java.lang.Number parse(java.lang.String, java.text.ParsePosition); - method public synchronized void setCurrency(android.icu.util.Currency); method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo); method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage); method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols); @@ -18091,15 +18079,10 @@ package android.icu.text { method public synchronized void setExponentSignAlwaysShown(boolean); method public synchronized void setFormatWidth(int); method public synchronized void setGroupingSize(int); - method public synchronized void setGroupingUsed(boolean); method public synchronized void setMathContext(java.math.MathContext); method public synchronized void setMathContextICU(android.icu.math.MathContext); - method public synchronized void setMaximumFractionDigits(int); - method public synchronized void setMaximumIntegerDigits(int); method public synchronized void setMaximumSignificantDigits(int); method public synchronized void setMinimumExponentDigits(byte); - method public synchronized void setMinimumFractionDigits(int); - method public synchronized void setMinimumIntegerDigits(int); method public synchronized void setMinimumSignificantDigits(int); method public synchronized void setMultiplier(int); method public synchronized void setNegativePrefix(java.lang.String); @@ -18107,15 +18090,12 @@ package android.icu.text { method public synchronized void setPadCharacter(char); method public synchronized void setPadPosition(int); method public synchronized void setParseBigDecimal(boolean); - method public synchronized void setParseIntegerOnly(boolean); method public deprecated void setParseMaxDigits(int); - method public synchronized void setParseStrict(boolean); method public synchronized void setPositivePrefix(java.lang.String); method public synchronized void setPositiveSuffix(java.lang.String); method public synchronized void setRoundingIncrement(java.math.BigDecimal); method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal); method public synchronized void setRoundingIncrement(double); - method public synchronized void setRoundingMode(int); method public synchronized void setScientificNotation(boolean); method public synchronized void setSecondaryGroupingSize(int); method public synchronized void setSignificantDigitsUsed(boolean); @@ -25557,7 +25537,6 @@ package android.net { public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable { method public void close(); - method protected void finalize(); method public int getSpi(); } @@ -25712,6 +25691,7 @@ package android.net { field public static final int NET_CAPABILITY_MMS = 0; // 0x0 field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf field public static final int NET_CAPABILITY_RCS = 8; // 0x8 field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 @@ -25742,7 +25722,7 @@ package android.net { method public boolean isConnected(); method public boolean isConnectedOrConnecting(); method public boolean isFailover(); - method public boolean isRoaming(); + method public deprecated boolean isRoaming(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR; } @@ -39615,6 +39595,8 @@ package android.telephony { method public int getLatitude(); method public int getLongitude(); method public int getNetworkId(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getSystemId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR; @@ -39626,8 +39608,13 @@ package android.telephony { method public int getBsic(); method public int getCid(); method public int getLac(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public deprecated int getPsc(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR; @@ -39637,8 +39624,13 @@ package android.telephony { method public int describeContents(); method public int getCi(); method public int getEarfcn(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getPci(); method public int getTac(); method public void writeToParcel(android.os.Parcel, int); @@ -39649,8 +39641,13 @@ package android.telephony { method public int describeContents(); method public int getCid(); method public int getLac(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getPsc(); method public int getUarfcn(); method public void writeToParcel(android.os.Parcel, int); diff --git a/api/system-current.txt b/api/system-current.txt index 065d637dc2f5..8f19a687c34d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -19548,13 +19548,11 @@ package android.icu.text { method public synchronized void applyLocalizedPattern(java.lang.String); method public synchronized void applyPattern(java.lang.String); method public synchronized boolean areSignificantDigitsUsed(); - method public synchronized boolean equals(java.lang.Object); method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition); - method public synchronized android.icu.util.Currency getCurrency(); method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo(); method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage(); method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols(); @@ -19562,12 +19560,8 @@ package android.icu.text { method public synchronized int getGroupingSize(); method public synchronized java.math.MathContext getMathContext(); method public synchronized android.icu.math.MathContext getMathContextICU(); - method public synchronized int getMaximumFractionDigits(); - method public synchronized int getMaximumIntegerDigits(); method public synchronized int getMaximumSignificantDigits(); method public synchronized byte getMinimumExponentDigits(); - method public synchronized int getMinimumFractionDigits(); - method public synchronized int getMinimumIntegerDigits(); method public synchronized int getMinimumSignificantDigits(); method public synchronized int getMultiplier(); method public synchronized java.lang.String getNegativePrefix(); @@ -19578,19 +19572,13 @@ package android.icu.text { method public synchronized java.lang.String getPositivePrefix(); method public synchronized java.lang.String getPositiveSuffix(); method public synchronized java.math.BigDecimal getRoundingIncrement(); - method public synchronized int getRoundingMode(); method public synchronized int getSecondaryGroupingSize(); - method public synchronized int hashCode(); method public synchronized boolean isDecimalPatternMatchRequired(); method public synchronized boolean isDecimalSeparatorAlwaysShown(); method public synchronized boolean isExponentSignAlwaysShown(); - method public synchronized boolean isGroupingUsed(); method public synchronized boolean isParseBigDecimal(); - method public synchronized boolean isParseIntegerOnly(); - method public synchronized boolean isParseStrict(); method public synchronized boolean isScientificNotation(); method public java.lang.Number parse(java.lang.String, java.text.ParsePosition); - method public synchronized void setCurrency(android.icu.util.Currency); method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo); method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage); method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols); @@ -19599,15 +19587,10 @@ package android.icu.text { method public synchronized void setExponentSignAlwaysShown(boolean); method public synchronized void setFormatWidth(int); method public synchronized void setGroupingSize(int); - method public synchronized void setGroupingUsed(boolean); method public synchronized void setMathContext(java.math.MathContext); method public synchronized void setMathContextICU(android.icu.math.MathContext); - method public synchronized void setMaximumFractionDigits(int); - method public synchronized void setMaximumIntegerDigits(int); method public synchronized void setMaximumSignificantDigits(int); method public synchronized void setMinimumExponentDigits(byte); - method public synchronized void setMinimumFractionDigits(int); - method public synchronized void setMinimumIntegerDigits(int); method public synchronized void setMinimumSignificantDigits(int); method public synchronized void setMultiplier(int); method public synchronized void setNegativePrefix(java.lang.String); @@ -19615,15 +19598,12 @@ package android.icu.text { method public synchronized void setPadCharacter(char); method public synchronized void setPadPosition(int); method public synchronized void setParseBigDecimal(boolean); - method public synchronized void setParseIntegerOnly(boolean); method public deprecated void setParseMaxDigits(int); - method public synchronized void setParseStrict(boolean); method public synchronized void setPositivePrefix(java.lang.String); method public synchronized void setPositiveSuffix(java.lang.String); method public synchronized void setRoundingIncrement(java.math.BigDecimal); method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal); method public synchronized void setRoundingIncrement(double); - method public synchronized void setRoundingMode(int); method public synchronized void setScientificNotation(boolean); method public synchronized void setSecondaryGroupingSize(int); method public synchronized void setSignificantDigitsUsed(boolean); @@ -27753,7 +27733,6 @@ package android.net { public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable { method public void close(); - method protected void finalize(); method public int getSpi(); } @@ -27910,6 +27889,7 @@ package android.net { field public static final int NET_CAPABILITY_MMS = 0; // 0x0 field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf field public static final int NET_CAPABILITY_RCS = 8; // 0x8 field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 @@ -27940,7 +27920,7 @@ package android.net { method public boolean isConnected(); method public boolean isConnectedOrConnecting(); method public boolean isFailover(); - method public boolean isRoaming(); + method public deprecated boolean isRoaming(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR; } @@ -43046,6 +43026,8 @@ package android.telephony { method public int getLatitude(); method public int getLongitude(); method public int getNetworkId(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getSystemId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR; @@ -43057,8 +43039,13 @@ package android.telephony { method public int getBsic(); method public int getCid(); method public int getLac(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public deprecated int getPsc(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR; @@ -43068,8 +43055,13 @@ package android.telephony { method public int describeContents(); method public int getCi(); method public int getEarfcn(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getPci(); method public int getTac(); method public void writeToParcel(android.os.Parcel, int); @@ -43080,8 +43072,13 @@ package android.telephony { method public int describeContents(); method public int getCid(); method public int getLac(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getPsc(); method public int getUarfcn(); method public void writeToParcel(android.os.Parcel, int); diff --git a/api/test-current.txt b/api/test-current.txt index 016990cc5aa4..9280e04d3287 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -18092,13 +18092,11 @@ package android.icu.text { method public synchronized void applyLocalizedPattern(java.lang.String); method public synchronized void applyPattern(java.lang.String); method public synchronized boolean areSignificantDigitsUsed(); - method public synchronized boolean equals(java.lang.Object); method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition); method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition); - method public synchronized android.icu.util.Currency getCurrency(); method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo(); method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage(); method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols(); @@ -18106,12 +18104,8 @@ package android.icu.text { method public synchronized int getGroupingSize(); method public synchronized java.math.MathContext getMathContext(); method public synchronized android.icu.math.MathContext getMathContextICU(); - method public synchronized int getMaximumFractionDigits(); - method public synchronized int getMaximumIntegerDigits(); method public synchronized int getMaximumSignificantDigits(); method public synchronized byte getMinimumExponentDigits(); - method public synchronized int getMinimumFractionDigits(); - method public synchronized int getMinimumIntegerDigits(); method public synchronized int getMinimumSignificantDigits(); method public synchronized int getMultiplier(); method public synchronized java.lang.String getNegativePrefix(); @@ -18122,19 +18116,13 @@ package android.icu.text { method public synchronized java.lang.String getPositivePrefix(); method public synchronized java.lang.String getPositiveSuffix(); method public synchronized java.math.BigDecimal getRoundingIncrement(); - method public synchronized int getRoundingMode(); method public synchronized int getSecondaryGroupingSize(); - method public synchronized int hashCode(); method public synchronized boolean isDecimalPatternMatchRequired(); method public synchronized boolean isDecimalSeparatorAlwaysShown(); method public synchronized boolean isExponentSignAlwaysShown(); - method public synchronized boolean isGroupingUsed(); method public synchronized boolean isParseBigDecimal(); - method public synchronized boolean isParseIntegerOnly(); - method public synchronized boolean isParseStrict(); method public synchronized boolean isScientificNotation(); method public java.lang.Number parse(java.lang.String, java.text.ParsePosition); - method public synchronized void setCurrency(android.icu.util.Currency); method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo); method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage); method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols); @@ -18143,15 +18131,10 @@ package android.icu.text { method public synchronized void setExponentSignAlwaysShown(boolean); method public synchronized void setFormatWidth(int); method public synchronized void setGroupingSize(int); - method public synchronized void setGroupingUsed(boolean); method public synchronized void setMathContext(java.math.MathContext); method public synchronized void setMathContextICU(android.icu.math.MathContext); - method public synchronized void setMaximumFractionDigits(int); - method public synchronized void setMaximumIntegerDigits(int); method public synchronized void setMaximumSignificantDigits(int); method public synchronized void setMinimumExponentDigits(byte); - method public synchronized void setMinimumFractionDigits(int); - method public synchronized void setMinimumIntegerDigits(int); method public synchronized void setMinimumSignificantDigits(int); method public synchronized void setMultiplier(int); method public synchronized void setNegativePrefix(java.lang.String); @@ -18159,15 +18142,12 @@ package android.icu.text { method public synchronized void setPadCharacter(char); method public synchronized void setPadPosition(int); method public synchronized void setParseBigDecimal(boolean); - method public synchronized void setParseIntegerOnly(boolean); method public deprecated void setParseMaxDigits(int); - method public synchronized void setParseStrict(boolean); method public synchronized void setPositivePrefix(java.lang.String); method public synchronized void setPositiveSuffix(java.lang.String); method public synchronized void setRoundingIncrement(java.math.BigDecimal); method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal); method public synchronized void setRoundingIncrement(double); - method public synchronized void setRoundingMode(int); method public synchronized void setScientificNotation(boolean); method public synchronized void setSecondaryGroupingSize(int); method public synchronized void setSignificantDigitsUsed(boolean); @@ -25667,7 +25647,6 @@ package android.net { public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable { method public void close(); - method protected void finalize(); method public int getSpi(); } @@ -25822,6 +25801,7 @@ package android.net { field public static final int NET_CAPABILITY_MMS = 0; // 0x0 field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd + field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf field public static final int NET_CAPABILITY_RCS = 8; // 0x8 field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 @@ -25852,7 +25832,7 @@ package android.net { method public boolean isConnected(); method public boolean isConnectedOrConnecting(); method public boolean isFailover(); - method public boolean isRoaming(); + method public deprecated boolean isRoaming(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.NetworkInfo> CREATOR; } @@ -39839,6 +39819,8 @@ package android.telephony { method public int getLatitude(); method public int getLongitude(); method public int getNetworkId(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getSystemId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR; @@ -39850,8 +39832,13 @@ package android.telephony { method public int getBsic(); method public int getCid(); method public int getLac(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public deprecated int getPsc(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR; @@ -39861,8 +39848,13 @@ package android.telephony { method public int describeContents(); method public int getCi(); method public int getEarfcn(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getPci(); method public int getTac(); method public void writeToParcel(android.os.Parcel, int); @@ -39873,8 +39865,13 @@ package android.telephony { method public int describeContents(); method public int getCid(); method public int getLac(); - method public int getMcc(); - method public int getMnc(); + method public deprecated int getMcc(); + method public java.lang.String getMccStr(); + method public deprecated int getMnc(); + method public java.lang.String getMncStr(); + method public java.lang.String getMobileNetworkOperator(); + method public java.lang.CharSequence getOperatorAlphaLong(); + method public java.lang.CharSequence getOperatorAlphaShort(); method public int getPsc(); method public int getUarfcn(); method public void writeToParcel(android.os.Parcel, int); @@ -40026,6 +40023,7 @@ package android.telephony { field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST"; field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_RESULT = "android.telephony.extra.MBMS_DOWNLOAD_RESULT"; field public static final java.lang.String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO"; + field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = "mbms-download-service-override"; field public static final int RESULT_CANCELLED = 2; // 0x2 field public static final int RESULT_DOWNLOAD_FAILURE = 6; // 0x6 field public static final int RESULT_EXPIRED = 3; // 0x3 @@ -40047,6 +40045,7 @@ package android.telephony { method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler); method public void requestUpdateStreamingServices(java.util.List<java.lang.String>); method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler); + field public static final java.lang.String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override"; } public class NeighboringCellInfo implements android.os.Parcelable { @@ -40724,6 +40723,7 @@ package android.telephony.mbms { } public final class FileServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable { + ctor public FileServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date, java.util.List<android.telephony.mbms.FileInfo>); method public int describeContents(); method public java.util.List<android.telephony.mbms.FileInfo> getFiles(); method public void writeToParcel(android.os.Parcel, int); @@ -40822,6 +40822,7 @@ package android.telephony.mbms { } public final class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable { + ctor public StreamingServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR; @@ -40829,6 +40830,37 @@ package android.telephony.mbms { } +package android.telephony.mbms.vendor { + + public class MbmsDownloadServiceBase extends android.os.Binder { + ctor public MbmsDownloadServiceBase(); + method public int cancelDownload(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException; + method public void dispose(int) throws android.os.RemoteException; + method public int download(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException; + method public int getDownloadStatus(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo) throws android.os.RemoteException; + method public int initialize(int, android.telephony.mbms.MbmsDownloadSessionCallback) throws android.os.RemoteException; + method public java.util.List<android.telephony.mbms.DownloadRequest> listPendingDownloads(int) throws android.os.RemoteException; + method public void onAppCallbackDied(int, int); + method public int registerStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback) throws android.os.RemoteException; + method public int requestUpdateFileServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException; + method public int resetDownloadKnowledge(android.telephony.mbms.DownloadRequest) throws android.os.RemoteException; + method public int setTempFileRootDirectory(int, java.lang.String) throws android.os.RemoteException; + method public int unregisterStateCallback(android.telephony.mbms.DownloadRequest, android.telephony.mbms.DownloadStateCallback) throws android.os.RemoteException; + } + + public class MbmsStreamingServiceBase extends android.os.Binder { + ctor public MbmsStreamingServiceBase(); + method public void dispose(int) throws android.os.RemoteException; + method public android.net.Uri getPlaybackUri(int, java.lang.String) throws android.os.RemoteException; + method public int initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int) throws android.os.RemoteException; + method public void onAppCallbackDied(int, int); + method public int requestUpdateStreamingServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException; + method public int startStreaming(int, java.lang.String, android.telephony.mbms.StreamingServiceCallback) throws android.os.RemoteException; + method public void stopStreaming(int, java.lang.String) throws android.os.RemoteException; + } + +} + package android.test { public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase { diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java index 5f61e07ec46e..d15dd6d71f94 100644 --- a/core/java/android/app/DexLoadReporter.java +++ b/core/java/android/app/DexLoadReporter.java @@ -19,6 +19,7 @@ package android.app; import android.os.FileUtils; import android.os.RemoteException; import android.os.SystemProperties; +import android.system.ErrnoException; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -26,10 +27,11 @@ import com.android.internal.annotations.GuardedBy; import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; +import libcore.io.Libcore; + import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -114,13 +116,18 @@ import java.util.Set; registerSecondaryDexForProfiling(dexPathsForRegistration); } - private void notifyPackageManager(List<BaseDexClassLoader> ignored, + private void notifyPackageManager(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths) { + // Get the class loader names for the binder call. + List<String> classLoadersNames = new ArrayList<>(classPaths.size()); + for (BaseDexClassLoader bdc : classLoadersChain) { + classLoadersNames.add(bdc.getClass().getName()); + } String packageName = ActivityThread.currentPackageName(); try { // Notify only the paths of the first class loader for now. ActivityThread.getPackageManager().notifyDexLoad( - packageName, Arrays.asList(classPaths.get(0).split(File.pathSeparator)), + packageName, classLoadersNames, classPaths, VMRuntime.getRuntime().vmInstructionSet()); } catch (RemoteException re) { Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); @@ -148,22 +155,50 @@ import java.util.Set; // The dex path is not a secondary dex file. Nothing to do. return; } - File secondaryProfile = getSecondaryProfileFile(dexPath); + + File realDexPath; + try { + // Secondary dex profiles are stored in the oat directory, next to the real dex file + // and have the same name with 'cur.prof' appended. We use the realpath because that + // is what installd is using when processing the dex file. + // NOTE: Keep in sync with installd. + realDexPath = new File(Libcore.os.realpath(dexPath)); + } catch (ErrnoException ex) { + Slog.e(TAG, "Failed to get the real path of secondary dex " + dexPath + + ":" + ex.getMessage()); + // Do not continue with registration if we could not retrieve the real path. + return; + } + + // NOTE: Keep this in sync with installd expectations. + File secondaryProfileDir = new File(realDexPath.getParent(), "oat"); + File secondaryProfile = new File(secondaryProfileDir, realDexPath.getName() + ".cur.prof"); + + // Create the profile if not already there. + // Returns true if the file was created, false if the file already exists. + // or throws exceptions in case of errors. + if (!secondaryProfileDir.exists()) { + if (!secondaryProfileDir.mkdir()) { + Slog.e(TAG, "Could not create the profile directory: " + secondaryProfile); + // Do not continue with registration if we could not create the oat dir. + return; + } + } + try { - // Create the profile if not already there. - // Returns true if the file was created, false if the file already exists. - // or throws exceptions in case of errors. boolean created = secondaryProfile.createNewFile(); if (DEBUG && created) { Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile); } } catch (IOException ex) { - Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile + - ":" + ex.getMessage()); - // Don't move forward with the registration if we failed to create the profile. + Slog.e(TAG, "Failed to create profile for secondary dex " + dexPath + + ":" + ex.getMessage()); + // Do not continue with registration if we could not create the profile files. return; } + // If we got here, the dex paths is a secondary dex and we were able to create the profile. + // Register the path to the runtime. VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath }); } @@ -177,11 +212,4 @@ import java.util.Set; } return false; } - - // Secondary dex profiles are stored next to the dex file and have the same - // name with '.prof' appended. - // NOTE: Keep in sync with installd. - private File getSecondaryProfileFile(String dexPath) { - return new File(dexPath + ".prof"); - } } diff --git a/core/java/android/app/timezone/RulesUpdaterContract.java b/core/java/android/app/timezone/RulesUpdaterContract.java index 9c62f46b6e36..1f354af3f0f8 100644 --- a/core/java/android/app/timezone/RulesUpdaterContract.java +++ b/core/java/android/app/timezone/RulesUpdaterContract.java @@ -83,8 +83,7 @@ public final class RulesUpdaterContract { Intent intent = createUpdaterIntent(updaterAppPackageName); intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes); context.sendBroadcastAsUser( - intent, - UserHandle.of(UserHandle.myUserId()), + intent, UserHandle.SYSTEM, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION); } } diff --git a/core/java/android/app/usage/ExternalStorageStats.java b/core/java/android/app/usage/ExternalStorageStats.java index d7e570fea4d7..f00e5c29cd75 100644 --- a/core/java/android/app/usage/ExternalStorageStats.java +++ b/core/java/android/app/usage/ExternalStorageStats.java @@ -33,6 +33,7 @@ public final class ExternalStorageStats implements Parcelable { /** {@hide} */ public long videoBytes; /** {@hide} */ public long imageBytes; /** {@hide} */ public long appBytes; + /** {@hide} */ public long obbBytes; /** * Return the total bytes used by all files in the shared/external storage @@ -97,6 +98,11 @@ public final class ExternalStorageStats implements Parcelable { } /** {@hide} */ + public @BytesLong long getObbBytes() { + return obbBytes; + } + + /** {@hide} */ public ExternalStorageStats() { } @@ -107,6 +113,7 @@ public final class ExternalStorageStats implements Parcelable { this.videoBytes = in.readLong(); this.imageBytes = in.readLong(); this.appBytes = in.readLong(); + this.obbBytes = in.readLong(); } @Override @@ -121,6 +128,7 @@ public final class ExternalStorageStats implements Parcelable { dest.writeLong(videoBytes); dest.writeLong(imageBytes); dest.writeLong(appBytes); + dest.writeLong(obbBytes); } public static final Creator<ExternalStorageStats> CREATOR = new Creator<ExternalStorageStats>() { diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java index 179f36dab32f..e3d763ab9b1e 100644 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -31,7 +31,14 @@ import java.util.Arrays; import java.util.List; /** - * @hide + * Provides the public APIs to control the Bluetooth HID Device + * profile. + * + * BluetoothHidDevice is a proxy object for controlling the Bluetooth HID + * Device Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothHidDevice proxy object. + * + * {@hide} */ public final class BluetoothHidDevice implements BluetoothProfile { @@ -62,7 +69,9 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Constants representing device subclass. * - * @see #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback) + * @see #registerApp + * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, + * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback) */ public static final byte SUBCLASS1_NONE = (byte) 0x00; public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40; @@ -80,9 +89,9 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Constants representing report types. * - * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int) - * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[]) - * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[]) + * @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int) + * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[]) + * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[]) */ public static final byte REPORT_TYPE_INPUT = (byte) 1; public static final byte REPORT_TYPE_OUTPUT = (byte) 2; @@ -91,7 +100,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Constants representing error response for Set Report. * - * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[]) + * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[]) */ public static final byte ERROR_RSP_SUCCESS = (byte) 0; public static final byte ERROR_RSP_NOT_READY = (byte) 1; @@ -104,7 +113,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { * Constants representing protocol mode used set by host. Default is always * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise. * - * @see BluetoothHidDeviceCallback#onSetProtocol(byte) + * @see BluetoothHidDeviceCallback#onSetProtocol(BluetoothDevice, byte) */ public static final byte PROTOCOL_BOOT_MODE = (byte) 0; public static final byte PROTOCOL_REPORT_MODE = (byte) 1; @@ -169,18 +178,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { public void onBluetoothStateChange(boolean up) { Log.d(TAG, "onBluetoothStateChange: up=" + up); synchronized (mConnection) { - if (!up) { - Log.d(TAG, "Unbinding service..."); - if (mService != null) { - mService = null; - try { - mContext.unbindService(mConnection); - } catch (IllegalArgumentException e) { - Log.e(TAG, "onBluetoothStateChange: could not unbind service:", - e); - } - } - } else { + if (up) { try { if (mService == null) { Log.d(TAG, "Binding HID Device service..."); @@ -189,14 +187,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { } catch (IllegalStateException e) { Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev " - + "service: ", - e); + + "service: ", e); } catch (SecurityException e) { Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev " - + "service: ", - e); + + "service: ", e); } + } else { + Log.d(TAG, "Unbinding service..."); + doUnbind(); } } } @@ -252,6 +251,18 @@ public final class BluetoothHidDevice implements BluetoothProfile { return true; } + void doUnbind() { + Log.d(TAG, "Unbinding HidDevService"); + if (mService != null) { + mService = null; + try { + mContext.unbindService(mConnection); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Unable to unbind HidDevService", e); + } + } + } + void close() { Log.v(TAG, "close()"); @@ -265,16 +276,8 @@ public final class BluetoothHidDevice implements BluetoothProfile { } synchronized (mConnection) { - if (mService != null) { - mService = null; - try { - mContext.unbindService(mConnection); - } catch (IllegalArgumentException e) { - Log.e(TAG, "close: could not unbind HID Dev service: ", e); - } - } + doUnbind(); } - mServiceListener = null; } @@ -388,7 +391,9 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** * Unregisters application. Active connection will be disconnected and no * new connections will be allowed until registered again using - * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)} + * {@link #registerApp + * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings, + * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)} * * @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link * BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice, diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java index 2731935a4ea7..d1efa2d6fca6 100644 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java +++ b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java @@ -21,7 +21,16 @@ import android.os.Parcelable; import java.util.Random; -/** @hide */ +/** + * Represents the app configuration for a Bluetooth HID Device application. + * + * The app needs a BluetoothHidDeviceAppConfiguration token to unregister + * the Bluetooth HID Device service. + * + * {@see BluetoothHidDevice} + * + * {@hide} + */ public final class BluetoothHidDeviceAppConfiguration implements Parcelable { private final long mHash; diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java index 1f80ed78d75d..ccc3ef4008fa 100644 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java +++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java @@ -19,7 +19,17 @@ package android.bluetooth; import android.os.Parcel; import android.os.Parcelable; -/** @hide */ +/** + * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device + * application. + * + * The BluetoothHidDevice framework will update the L2CAP QoS settings for the + * app during registration. + * + * {@see BluetoothHidDevice} + * + * {@hide} + */ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { public final int serviceType; @@ -36,8 +46,7 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { public static final int MAX = (int) 0xffffffff; public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize, - int peakBandwidth, - int latency, int delayVariation) { + int peakBandwidth, int latency, int delayVariation) { this.serviceType = serviceType; this.tokenRate = tokenRate; this.tokenBucketSize = tokenBucketSize; @@ -66,10 +75,13 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { @Override public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) { - return new BluetoothHidDeviceAppQosSettings(in.readInt(), in.readInt(), + return new BluetoothHidDeviceAppQosSettings( + in.readInt(), + in.readInt(), + in.readInt(), in.readInt(), in.readInt(), - in.readInt(), in.readInt()); + in.readInt()); } @Override @@ -90,7 +102,7 @@ public final class BluetoothHidDeviceAppQosSettings implements Parcelable { /** @return an int array representation of this instance */ public int[] toArray() { - return new int[]{ + return new int[] { serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation }; } diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java index d21d50611ab0..f01c49328968 100644 --- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java +++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java @@ -19,7 +19,18 @@ package android.bluetooth; import android.os.Parcel; import android.os.Parcelable; -/** @hide */ +/** + * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth + * HID Device application. + * + * The BluetoothHidDevice framework adds the SDP record during app + * registration, so that the Android device can be discovered as a Bluetooth + * HID Device. + * + * {@see BluetoothHidDevice} + * + * {@hide} + */ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { public final String name; @@ -57,8 +68,12 @@ public final class BluetoothHidDeviceAppSdpSettings implements Parcelable { @Override public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) { - return new BluetoothHidDeviceAppSdpSettings(in.readString(), in.readString(), - in.readString(), in.readByte(), in.createByteArray()); + return new BluetoothHidDeviceAppSdpSettings( + in.readString(), + in.readString(), + in.readString(), + in.readByte(), + in.createByteArray()); } @Override diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java index 3d407a6ca733..5ccda0dc7dd5 100644 --- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java +++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java @@ -18,16 +18,24 @@ package android.bluetooth; import android.util.Log; -/** @hide */ +/** + * The template class that applications use to call callback functions on + * events from the HID host. Callback functions are wrapped in this class and + * registered to the Android system during app registration. + * + * {@see BluetoothHidDevice} + * + * {@hide} + */ public abstract class BluetoothHidDeviceCallback { - private static final String TAG = BluetoothHidDeviceCallback.class.getSimpleName(); + private static final String TAG = "BluetoothHidDevCallback"; /** * Callback called when application registration state changes. Usually it's * called due to either - * {@link BluetoothHidDevice#registerApp(String, String, String, byte, byte[], - * BluetoothHidDeviceCallback)} + * {@link BluetoothHidDevice#registerApp + * (String, String, String, byte, byte[], BluetoothHidDeviceCallback)} * or * {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)} * , but can be also unsolicited in case e.g. Bluetooth was turned off in @@ -79,7 +87,7 @@ public abstract class BluetoothHidDeviceCallback { /** * Callback called when SET_REPORT is received from remote host. In case * received data are invalid, application shall respond with - * {@link BluetoothHidDevice#reportError(BluetoothDevice)}. + * {@link BluetoothHidDevice#reportError(BluetoothDevice, byte)}. * * @param type Report Type. * @param id Report Id. diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 06f7916be4b6..70af021fe0c3 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -688,6 +688,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Cycles do not exist because they are illegal and screened for during installation. * * May be null if no splits are installed, or if no dependencies exist between them. + * + * NOTE: Any change to the way split dependencies are stored must update the logic that + * creates the class loader context for dexopt (DexoptUtils#getClassLoaderContexts). + * * @hide */ public SparseArray<int[]> splitDependencies; diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 4b44a177ec20..c9afd6b7e930 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -469,11 +469,19 @@ interface IPackageManager { * Notify the package manager that a list of dex files have been loaded. * * @param loadingPackageName the name of the package who performs the load - * @param dexPats the list of the dex files paths that have been loaded + * @param classLoadersNames the names of the class loaders present in the loading chain. The + * list encodes the class loader chain in the natural order. The first class loader has + * the second one as its parent and so on. The dex files present in the class path of the + * first class loader will be recorded in the usage file. + * @param classPaths the class paths corresponding to the class loaders names from + * {@param classLoadersNames}. The the first element corresponds to the first class loader + * and so on. A classpath is represented as a list of dex files separated by + * {@code File.pathSeparator}. + * The dex files found in the first class path will be recorded in the usage file. * @param loaderIsa the ISA of the loader process */ - oneway void notifyDexLoad(String loadingPackageName, in List<String> dexPaths, - String loaderIsa); + oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames, + in List<String> classPaths, String loaderIsa); /** * Register an application dex module with the package manager. @@ -508,21 +516,13 @@ interface IPackageManager { in boolean isSharedModule, IDexModuleRegisterCallback callback); /** - * Ask the package manager to perform a dex-opt for the given reason. The package - * manager will map the reason to a compiler filter according to the current system - * configuration. - */ - boolean performDexOpt(String packageName, boolean checkProfiles, - int compileReason, boolean force, boolean bootComplete); - - /** * Ask the package manager to perform a dex-opt with the given compiler filter. * * Note: exposed only for the shell command to allow moving packages explicitly to a * definite state. */ boolean performDexOptMode(String packageName, boolean checkProfiles, - String targetCompilerFilter, boolean force, boolean bootComplete); + String targetCompilerFilter, boolean force, boolean bootComplete, String splitName); /** * Ask the package manager to perform a dex-opt with the given compiler filter on the diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index 3c868c399b1f..903b602b42f8 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -16,14 +16,14 @@ package android.net; -import android.os.Parcelable; import android.os.Parcel; +import android.os.Parcelable; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; -import libcore.net.http.Dns; -import libcore.net.http.HttpURLConnectionFactory; +import com.android.okhttp.internalandroidapi.Dns; +import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory; import java.io.FileDescriptor; import java.io.IOException; @@ -34,11 +34,12 @@ import java.net.MalformedURLException; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; -import java.net.UnknownHostException; import java.net.URL; import java.net.URLConnection; +import java.net.UnknownHostException; import java.util.Arrays; import java.util.concurrent.TimeUnit; + import javax.net.SocketFactory; /** diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index db12dd9724dc..ee75fd443052 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; +import android.net.ConnectivityManager.NetworkCallback; import android.os.Parcel; import android.os.Parcelable; @@ -30,15 +31,24 @@ import java.util.Objects; import java.util.StringJoiner; /** - * This class represents the capabilities of a network. This is used both to specify - * needs to {@link ConnectivityManager} and when inspecting a network. - * - * Note that this replaces the old {@link ConnectivityManager#TYPE_MOBILE} method - * of network selection. Rather than indicate a need for Wi-Fi because an application - * needs high bandwidth and risk obsolescence when a new, fast network appears (like LTE), - * the application should specify it needs high bandwidth. Similarly if an application - * needs an unmetered network for a bulk transfer it can specify that rather than assuming - * all cellular based connections are metered and all Wi-Fi based connections are not. + * Representation of the capabilities of a network. This object serves two + * purposes: + * <ul> + * <li>An expression of the current capabilities of an active network, typically + * expressed through + * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} + * or {@link ConnectivityManager#getNetworkCapabilities(Network)}. + * <li>An expression of the future capabilities of a desired network, typically + * expressed through {@link NetworkRequest}. + * </ul> + * <p> + * This replaces the old {@link ConnectivityManager#TYPE_MOBILE} method of + * network selection. Rather than indicate a need for Wi-Fi because an + * application needs high bandwidth and risk obsolescence when a new, fast + * network appears (like LTE), the application should specify it needs high + * bandwidth. Similarly if an application needs an unmetered network for a bulk + * transfer it can specify that rather than assuming all cellular based + * connections are metered and all Wi-Fi based connections are not. */ public final class NetworkCapabilities implements Parcelable { private static final String TAG = "NetworkCapabilities"; @@ -101,6 +111,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_NOT_VPN, NET_CAPABILITY_VALIDATED, NET_CAPABILITY_CAPTIVE_PORTAL, + NET_CAPABILITY_NOT_ROAMING, NET_CAPABILITY_FOREGROUND, }) public @interface NetCapability { } @@ -218,11 +229,16 @@ public final class NetworkCapabilities implements Parcelable { public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; /** + * Indicates that this network is not roaming. + */ + public static final int NET_CAPABILITY_NOT_ROAMING = 18; + + /** * Indicates that this network is available for use by apps, and not a network that is being * kept up in the background to facilitate fast network switching. * @hide */ - public static final int NET_CAPABILITY_FOREGROUND = 18; + public static final int NET_CAPABILITY_FOREGROUND = 19; private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_FOREGROUND; @@ -237,6 +253,7 @@ public final class NetworkCapabilities implements Parcelable { (1 << NET_CAPABILITY_TRUSTED) | (1 << NET_CAPABILITY_VALIDATED) | (1 << NET_CAPABILITY_CAPTIVE_PORTAL) | + (1 << NET_CAPABILITY_NOT_ROAMING) | (1 << NET_CAPABILITY_FOREGROUND); /** @@ -316,6 +333,21 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Sets (or clears) the given capability on this {@link NetworkCapabilities} + * instance. + * + * @hide + */ + public NetworkCapabilities setCapability(@NetCapability int capability, boolean value) { + if (value) { + addCapability(capability); + } else { + removeCapability(capability); + } + return this; + } + + /** * Gets all the capabilities set on this {@code NetworkCapability} instance. * * @return an array of capability values for this instance. @@ -326,6 +358,15 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Sets all the capabilities set on this {@code NetworkCapability} instance. + * + * @hide + */ + public void setCapabilities(@NetCapability int[] capabilities) { + mNetworkCapabilities = BitUtils.packBits(capabilities); + } + + /** * Tests for the presence of a capabilitity on this instance. * * @param capability the capabilities to be tested for. @@ -515,6 +556,21 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Sets (or clears) the given transport on this {@link NetworkCapabilities} + * instance. + * + * @hide + */ + public NetworkCapabilities setTransportType(@Transport int transportType, boolean value) { + if (value) { + addTransportType(transportType); + } else { + removeTransportType(transportType); + } + return this; + } + + /** * Gets all the transports set on this {@code NetworkCapability} instance. * * @return an array of transport type values for this instance. @@ -525,6 +581,15 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Sets all the transports set on this {@code NetworkCapability} instance. + * + * @hide + */ + public void setTransportTypes(@Transport int[] transportTypes) { + mTransportTypes = BitUtils.packBits(transportTypes); + } + + /** * Tests for the presence of a transport on this instance. * * @param transportType the transport type to be tested for. @@ -549,12 +614,18 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Value indicating that link bandwidth is unspecified. + * @hide + */ + public static final int LINK_BANDWIDTH_UNSPECIFIED = 0; + + /** * Passive link bandwidth. This is a rough guide of the expected peak bandwidth * for the first hop on the given transport. It is not measured, but may take into account * link parameters (Radio technology, allocated channels, etc). */ - private int mLinkUpBandwidthKbps; - private int mLinkDownBandwidthKbps; + private int mLinkUpBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; + private int mLinkDownBandwidthKbps = LINK_BANDWIDTH_UNSPECIFIED; /** * Sets the upstream bandwidth for this network in Kbps. This always only refers to @@ -571,8 +642,9 @@ public final class NetworkCapabilities implements Parcelable { * @param upKbps the estimated first hop upstream (device to network) bandwidth. * @hide */ - public void setLinkUpstreamBandwidthKbps(int upKbps) { + public NetworkCapabilities setLinkUpstreamBandwidthKbps(int upKbps) { mLinkUpBandwidthKbps = upKbps; + return this; } /** @@ -600,8 +672,9 @@ public final class NetworkCapabilities implements Parcelable { * @param downKbps the estimated first hop downstream (network to device) bandwidth. * @hide */ - public void setLinkDownstreamBandwidthKbps(int downKbps) { + public NetworkCapabilities setLinkDownstreamBandwidthKbps(int downKbps) { mLinkDownBandwidthKbps = downKbps; + return this; } /** @@ -628,6 +701,20 @@ public final class NetworkCapabilities implements Parcelable { return (this.mLinkUpBandwidthKbps == nc.mLinkUpBandwidthKbps && this.mLinkDownBandwidthKbps == nc.mLinkDownBandwidthKbps); } + /** @hide */ + public static int minBandwidth(int a, int b) { + if (a == LINK_BANDWIDTH_UNSPECIFIED) { + return b; + } else if (b == LINK_BANDWIDTH_UNSPECIFIED) { + return a; + } else { + return Math.min(a, b); + } + } + /** @hide */ + public static int maxBandwidth(int a, int b) { + return Math.max(a, b); + } private NetworkSpecifier mNetworkSpecifier = null; @@ -708,8 +795,9 @@ public final class NetworkCapabilities implements Parcelable { * @param signalStrength the bearer-specific signal strength. * @hide */ - public void setSignalStrength(int signalStrength) { + public NetworkCapabilities setSignalStrength(int signalStrength) { mSignalStrength = signalStrength; + return this; } /** @@ -968,6 +1056,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_NOT_VPN: return "NOT_VPN"; case NET_CAPABILITY_VALIDATED: return "VALIDATED"; case NET_CAPABILITY_CAPTIVE_PORTAL: return "CAPTIVE_PORTAL"; + case NET_CAPABILITY_NOT_ROAMING: return "NOT_ROAMING"; case NET_CAPABILITY_FOREGROUND: return "FOREGROUND"; default: return Integer.toString(capability); } diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index 0775bdaaf380..df404b7dce27 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -189,7 +189,8 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { String subscriberId = null; String networkId = null; - boolean roaming = false; + boolean roaming = !state.networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); boolean metered = !state.networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_METERED); @@ -203,7 +204,6 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } subscriberId = state.subscriberId; - roaming = state.networkInfo.isRoaming(); } else if (type == TYPE_WIFI) { if (state.networkId != null) { diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 84c32bec8ef7..d5549387755d 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -307,11 +307,17 @@ public class NetworkInfo implements Parcelable { } /** - * Indicates whether the device is currently roaming on this network. - * When {@code true}, it suggests that use of data on this network - * may incur extra costs. + * Indicates whether the device is currently roaming on this network. When + * {@code true}, it suggests that use of data on this network may incur + * extra costs. + * * @return {@code true} if roaming is in effect, {@code false} otherwise. + * @deprecated Callers should switch to checking + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_ROAMING} + * instead, since that handles more complex situations, such as + * VPNs. */ + @Deprecated public boolean isRoaming() { synchronized (this) { return mIsRoaming; @@ -320,6 +326,7 @@ public class NetworkInfo implements Parcelable { /** {@hide} */ @VisibleForTesting + @Deprecated public void setRoaming(boolean isRoaming) { synchronized (this) { mIsRoaming = isRoaming; diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS index 0f1e2597f39e..d1ce60e9ff08 100644 --- a/core/java/android/net/OWNERS +++ b/core/java/android/net/OWNERS @@ -1,6 +1,6 @@ ek@google.com hugobenichi@google.com -jsharkey@google.com +jsharkey@android.com lorenzo@google.com satk@google.com silberst@google.com diff --git a/core/java/android/net/metrics/DefaultNetworkEvent.java b/core/java/android/net/metrics/DefaultNetworkEvent.java index eb61c1532d49..8ff8e4f38d6d 100644 --- a/core/java/android/net/metrics/DefaultNetworkEvent.java +++ b/core/java/android/net/metrics/DefaultNetworkEvent.java @@ -20,44 +20,72 @@ import static android.net.ConnectivityManager.NETID_UNSET; import android.net.NetworkCapabilities; +import com.android.internal.util.BitUtils; + +import java.util.StringJoiner; + /** * An event recorded by ConnectivityService when there is a change in the default network. * {@hide} */ public class DefaultNetworkEvent { - // The ID of the network that has become the new default or NETID_UNSET if none. + // The creation time in milliseconds of this DefaultNetworkEvent. + public final long creationTimeMs; + // The network ID of the network or NETID_UNSET if none. public int netId = NETID_UNSET; - // The list of transport types of the new default network, for example TRANSPORT_WIFI, as - // defined in NetworkCapabilities.java. - public int[] transportTypes = new int[0]; - // The ID of the network that was the default before or NETID_UNSET if none. - public int prevNetId = NETID_UNSET; - // Whether the previous network had IPv4/IPv6 connectivity. - public boolean prevIPv4; - public boolean prevIPv6; + // The list of transport types, as defined in NetworkCapabilities.java. + public int transports; + // The list of transport types of the last previous default network. + public int previousTransports; + // Whether the network has IPv4/IPv6 connectivity. + public boolean ipv4; + public boolean ipv6; + // The initial network score when this network became the default network. + public int initialScore; + // The initial network score when this network stopped being the default network. + public int finalScore; + // The total duration in milliseconds this network was the default network. + public long durationMs; + // The total duration in milliseconds this network was the default network and was validated. + public long validatedMs; + + public DefaultNetworkEvent(long timeMs) { + creationTimeMs = timeMs; + } + + /** Update the durationMs of this DefaultNetworkEvent for the given current time. */ + public void updateDuration(long timeMs) { + durationMs = timeMs - creationTimeMs; + } @Override public String toString() { - String prevNetwork = String.valueOf(prevNetId); - String newNetwork = String.valueOf(netId); - if (prevNetId != 0) { - prevNetwork += ":" + ipSupport(); + StringJoiner j = new StringJoiner(", ", "DefaultNetworkEvent(", ")"); + j.add("netId=" + netId); + for (int t : BitUtils.unpackBits(transports)) { + j.add(NetworkCapabilities.transportNameOf(t)); + } + j.add("ip=" + ipSupport()); + if (initialScore > 0) { + j.add("initial_score=" + initialScore); } - if (netId != 0) { - newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes); + if (finalScore > 0) { + j.add("final_score=" + finalScore); } - return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork); + j.add(String.format("duration=%.0fs", durationMs / 1000.0)); + j.add(String.format("validation=%4.1f%%", (validatedMs * 100.0) / durationMs)); + return j.toString(); } private String ipSupport() { - if (prevIPv4 && prevIPv6) { + if (ipv4 && ipv6) { return "IPv4v6"; } - if (prevIPv6) { + if (ipv6) { return "IPv6"; } - if (prevIPv4) { + if (ipv4) { return "IPv4"; } return "NONE"; diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 5d28251a43e8..3c1d83c44cde 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -27,12 +27,14 @@ import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; import libcore.io.IoUtils; +import libcore.util.NativeAllocationRegistry; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Modifier; +import java.util.ArrayList; /** * Base class for a remotable object, the core part of a lightweight @@ -90,6 +92,20 @@ public class Binder implements IBinder { */ private static volatile TransactionTracker sTransactionTracker = null; + /** + * Guestimate of native memory associated with a Binder. + */ + private static final int NATIVE_ALLOCATION_SIZE = 500; + + private static native long getNativeFinalizer(); + + // Use a Holder to allow static initialization of Binder in the boot image, and + // possibly to avoid some initialization ordering issues. + private static class NoImagePreloadHolder { + public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + Binder.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE); + } + // Transaction tracking code. /** @@ -188,8 +204,11 @@ public class Binder implements IBinder { } } - /* mObject is used by native code, do not remove or rename */ - private long mObject; + /** + * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null. + */ + private final long mObject; + private IInterface mOwner; private String mDescriptor; @@ -360,7 +379,8 @@ public class Binder implements IBinder { * Default constructor initializes the object. */ public Binder() { - init(); + mObject = getNativeBBinderHolder(); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject); if (FIND_POTENTIAL_LEAKS) { final Class<? extends Binder> klass = getClass(); @@ -638,14 +658,6 @@ public class Binder implements IBinder { return true; } - protected void finalize() throws Throwable { - try { - destroyBinder(); - } finally { - super.finalize(); - } - } - static void checkParcel(IBinder obj, int code, Parcel parcel, String msg) { if (CHECK_PARCEL_SIZE && parcel.dataSize() >= 800*1024) { // Trying to send > 800k, this is way too much @@ -669,8 +681,8 @@ public class Binder implements IBinder { } } - private native final void init(); - private native final void destroyBinder(); + private static native long getNativeBBinderHolder(); + private static native long getFinalizer(); // Entry point from android_util_Binder.cpp's onTransact private boolean execTransact(int code, long dataObj, long replyObj, @@ -738,11 +750,207 @@ public class Binder implements IBinder { */ final class BinderProxy implements IBinder { // See android_util_Binder.cpp for the native half of this. - // TODO: Consider using NativeAllocationRegistry instead of finalization. // Assume the process-wide default value when created volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking; + /* + * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies. + * We roll our own only because we need to lazily remove WeakReferences during accesses + * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable + * because we want weak values, not keys. + * Our hash table is never resized, but the number of entries is unlimited; + * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE. + * Not thread-safe. Client ensures there's a single access at a time. + */ + private static final class ProxyMap { + private static final int LOG_MAIN_INDEX_SIZE = 8; + private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE; + private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1; + + /** + * We next warn when we exceed this bucket size. + */ + private int mWarnBucketSize = 20; + + /** + * Increment mWarnBucketSize by WARN_INCREMENT each time we warn. + */ + private static final int WARN_INCREMENT = 10; + + /** + * Hash function tailored to native pointers. + * Returns a value < MAIN_INDEX_SIZE. + */ + private static int hash(long arg) { + return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK; + } + + /** + * Return the total number of pairs in the map. + */ + int size() { + int size = 0; + for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) { + if (a != null) { + size += a.size(); + } + } + return size; + } + + /** + * Remove ith entry from the hash bucket indicated by hash. + */ + private void remove(int hash, int index) { + Long[] keyArray = mMainIndexKeys[hash]; + ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash]; + int size = valueArray.size(); // KeyArray may have extra elements. + // Move last entry into empty slot, and truncate at end. + if (index != size - 1) { + keyArray[index] = keyArray[size - 1]; + valueArray.set(index, valueArray.get(size - 1)); + } + valueArray.remove(size - 1); + // Just leave key array entry; it's unused. We only trust the valueArray size. + } + + /** + * Look up the supplied key. If we have a non-cleared entry for it, return it. + */ + BinderProxy get(long key) { + int myHash = hash(key); + Long[] keyArray = mMainIndexKeys[myHash]; + if (keyArray == null) { + return null; + } + ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash]; + int bucketSize = valueArray.size(); + for (int i = 0; i < bucketSize; ++i) { + long foundKey = keyArray[i]; + if (key == foundKey) { + WeakReference<BinderProxy> wr = valueArray.get(i); + BinderProxy bp = wr.get(); + if (bp != null) { + return bp; + } else { + remove(myHash, i); + return null; + } + } + } + return null; + } + + private int mRandom; // A counter used to generate a "random" index. World's 2nd worst RNG. + + /** + * Add the key-value pair to the map. + * Requires that the indicated key is not already in the map. + */ + void set(long key, @NonNull BinderProxy value) { + int myHash = hash(key); + ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash]; + if (valueArray == null) { + valueArray = mMainIndexValues[myHash] = new ArrayList<>(); + mMainIndexKeys[myHash] = new Long[1]; + } + int size = valueArray.size(); + WeakReference<BinderProxy> newWr = new WeakReference<>(value); + // First look for a cleared reference. + // This ensures that ArrayList size is bounded by the maximum occupancy of + // that bucket. + for (int i = 0; i < size; ++i) { + if (valueArray.get(i).get() == null) { + valueArray.set(i, newWr); + Long[] keyArray = mMainIndexKeys[myHash]; + keyArray[i] = key; + if (i < size - 1) { + // "Randomly" check one of the remaining entries in [i+1, size), so that + // needlessly long buckets are eventually pruned. + int rnd = Math.floorMod(++mRandom, size - (i + 1)); + if (valueArray.get(i + 1 + rnd).get() == null) { + remove(myHash, i + 1 + rnd); + } + } + return; + } + } + valueArray.add(size, newWr); + Long[] keyArray = mMainIndexKeys[myHash]; + if (keyArray.length == size) { + // size >= 1, since we initially allocated one element + Long[] newArray = new Long[size + size / 2 + 2]; + System.arraycopy(keyArray, 0, newArray, 0, size); + newArray[size] = key; + mMainIndexKeys[myHash] = newArray; + } else { + keyArray[size] = key; + } + if (size >= mWarnBucketSize) { + Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size + + " total = " + size()); + mWarnBucketSize += WARN_INCREMENT; + } + } + + // Corresponding ArrayLists in the following two arrays always have the same size. + // They contain no empty entries. However WeakReferences in the values ArrayLists + // may have been cleared. + + // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) . + // The values ArrayList has the proper size(), the corresponding keys array + // is always at least the same size, but may be larger. + // If either a particular keys array, or the corresponding values ArrayList + // are null, then they both are. + private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][]; + private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues = + new ArrayList[MAIN_INDEX_SIZE]; + } + + private static ProxyMap sProxyMap = new ProxyMap(); + + /** + * Return a BinderProxy for IBinder. + * This method is thread-hostile! The (native) caller serializes getInstance() calls using + * gProxyLock. + * If we previously returned a BinderProxy bp for the same iBinder, and bp is still + * in use, then we return the same bp. + * + * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData. + * Takes ownership of nativeData iff <result>.mNativeData == nativeData. Caller will usually + * delete nativeData if that's not the case. + * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object. + */ + private static BinderProxy getInstance(long nativeData, long iBinder) { + BinderProxy result = sProxyMap.get(iBinder); + if (result == null) { + result = new BinderProxy(nativeData); + sProxyMap.set(iBinder, result); + } + return result; + } + + private BinderProxy(long nativeData) { + mNativeData = nativeData; + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeData); + } + + /** + * Guestimate of native memory associated with a BinderProxy. + * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector + * that points back to us. We guess high since it includes a GlobalRef, which + * may be in short supply. + */ + private static final int NATIVE_ALLOCATION_SIZE = 1000; + + // Use a Holder to allow static initialization of BinderProxy in the boot image, and + // to avoid some initialization ordering issues. + private static class NoImagePreloadHolder { + public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + BinderProxy.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE); + } + public native boolean pingBinder(); public native boolean isBinderAlive(); @@ -778,6 +986,7 @@ final class BinderProxy implements IBinder { } } + private static native long getNativeFinalizer(); public native String getInterfaceDescriptor() throws RemoteException; public native boolean transactNative(int code, Parcel data, Parcel reply, int flags) throws RemoteException; @@ -832,21 +1041,6 @@ final class BinderProxy implements IBinder { } } - BinderProxy() { - mSelf = new WeakReference(this); - } - - @Override - protected void finalize() throws Throwable { - try { - destroy(); - } finally { - super.finalize(); - } - } - - private native final void destroy(); - private static final void sendDeathNotice(DeathRecipient recipient) { if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient); try { @@ -858,19 +1052,9 @@ final class BinderProxy implements IBinder { } } - // This WeakReference to "this" is used only by native code to "attach" to the - // native IBinder object. - // Using WeakGlobalRefs instead currently appears unsafe, in that they can yield a - // non-null value after the BinderProxy is enqueued for finalization. - // Used only once immediately after construction. - // TODO: Consider making the extra native-to-java call to compute this on the fly. - final private WeakReference mSelf; - - // Native pointer to the wrapped native IBinder object. Counted as strong reference. - private long mObject; - - // Native pointer to native DeathRecipientList. Counted as strong reference. - // Basically owned by the JavaProxy object. Reference counted only because DeathRecipients - // hold a weak reference that can be temporarily promoted. - private long mOrgue; + /** + * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the + * native IBinder object, and a DeathRecipientList. + */ + private final long mNativeData; } diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 12932956a7b9..3286e6ec6ef9 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -26,7 +26,6 @@ import com.android.internal.util.FastPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.TypedProperties; -import dalvik.bytecode.OpcodeInfo; import dalvik.system.VMDebug; import org.apache.harmony.dalvik.ddmc.Chunk; @@ -51,8 +50,6 @@ import java.util.HashMap; import java.util.Map; - - /** * Provides various debugging methods for Android applications, including * tracing and allocation counts. @@ -1951,13 +1948,7 @@ public final class Debug */ @Deprecated public static class InstructionCount { - private static final int NUM_INSTR = - OpcodeInfo.MAXIMUM_PACKED_VALUE + 1; - - private int[] mCounts; - public InstructionCount() { - mCounts = new int[NUM_INSTR]; } /** @@ -1967,13 +1958,7 @@ public final class Debug * @return true if counting was started */ public boolean resetAndStart() { - try { - VMDebug.startInstructionCounting(); - VMDebug.resetInstructionCount(); - } catch (UnsupportedOperationException uoe) { - return false; - } - return true; + return false; } /** @@ -1981,13 +1966,7 @@ public final class Debug * counting process. */ public boolean collect() { - try { - VMDebug.stopInstructionCounting(); - VMDebug.getInstructionCount(mCounts); - } catch (UnsupportedOperationException uoe) { - return false; - } - return true; + return false; } /** @@ -1995,13 +1974,7 @@ public final class Debug * all threads). */ public int globalTotal() { - int count = 0; - - for (int i = 0; i < NUM_INSTR; i++) { - count += mCounts[i]; - } - - return count; + return 0; } /** @@ -2009,15 +1982,7 @@ public final class Debug * executed globally. */ public int globalMethodInvocations() { - int count = 0; - - for (int i = 0; i < NUM_INSTR; i++) { - if (OpcodeInfo.isInvoke(i)) { - count += mCounts[i]; - } - } - - return count; + return 0; } } diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java index 7dec4d724f15..3544ea1e03e3 100644 --- a/core/java/android/os/HidlSupport.java +++ b/core/java/android/os/HidlSupport.java @@ -156,4 +156,27 @@ public class HidlSupport { // Should not reach here. throw new UnsupportedOperationException(); } + + /** + * Test that two interfaces are equal. This is the Java equivalent to C++ + * interfacesEqual function. + * This essentially calls .equals on the internal binder objects (via Binder()). + * - If both interfaces are proxies, asBinder() returns a {@link HwRemoteBinder} + * object, and they are compared in {@link HwRemoteBinder#equals}. + * - If both interfaces are stubs, asBinder() returns the object itself. By default, + * auto-generated IFoo.Stub does not override equals(), but an implementation can + * optionally override it, and {@code interfacesEqual} will use it here. + */ + public static boolean interfacesEqual(IHwInterface lft, Object rgt) { + if (lft == rgt) { + return true; + } + if (lft == null || rgt == null) { + return false; + } + if (!(rgt instanceof IHwInterface)) { + return false; + } + return Objects.equals(lft.asBinder(), ((IHwInterface) rgt).asBinder()); + } } diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index 270e63f408a7..5e2a0815b60d 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -16,10 +16,10 @@ package android.os; -import java.util.ArrayList; -import java.util.NoSuchElementException; import libcore.util.NativeAllocationRegistry; +import java.util.NoSuchElementException; + /** @hide */ public abstract class HwBinder implements IHwBinder { private static final String TAG = "HwBinder"; @@ -46,9 +46,16 @@ public abstract class HwBinder implements IHwBinder { public native final void registerService(String serviceName) throws RemoteException; - public static native final IHwBinder getService( + public static final IHwBinder getService( String iface, String serviceName) + throws RemoteException, NoSuchElementException { + return getService(iface, serviceName, false /* retry */); + } + public static native final IHwBinder getService( + String iface, + String serviceName, + boolean retry) throws RemoteException, NoSuchElementException; public static native final void configureRpcThreadpool( diff --git a/core/java/android/os/HwBlob.java b/core/java/android/os/HwBlob.java index 88226f0a1665..5e9b9ae3d49a 100644 --- a/core/java/android/os/HwBlob.java +++ b/core/java/android/os/HwBlob.java @@ -43,6 +43,18 @@ public class HwBlob { public native final double getDouble(long offset); public native final String getString(long offset); + /** + The copyTo... methods copy the blob's data, starting from the given + byte offset, into the array. A total of "size" _elements_ are copied. + */ + public native final void copyToBoolArray(long offset, boolean[] array, int size); + public native final void copyToInt8Array(long offset, byte[] array, int size); + public native final void copyToInt16Array(long offset, short[] array, int size); + public native final void copyToInt32Array(long offset, int[] array, int size); + public native final void copyToInt64Array(long offset, long[] array, int size); + public native final void copyToFloatArray(long offset, float[] array, int size); + public native final void copyToDoubleArray(long offset, double[] array, int size); + public native final void putBool(long offset, boolean x); public native final void putInt8(long offset, byte x); public native final void putInt16(long offset, short x); @@ -52,6 +64,14 @@ public class HwBlob { public native final void putDouble(long offset, double x); public native final void putString(long offset, String x); + public native final void putBoolArray(long offset, boolean[] x); + public native final void putInt8Array(long offset, byte[] x); + public native final void putInt16Array(long offset, short[] x); + public native final void putInt32Array(long offset, int[] x); + public native final void putInt64Array(long offset, long[] x); + public native final void putFloatArray(long offset, float[] x); + public native final void putDoubleArray(long offset, double[] x); + public native final void putBlob(long offset, HwBlob blob); public native final long handle(); diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java index 2f89ce6270be..a07e42c720c5 100644 --- a/core/java/android/os/HwRemoteBinder.java +++ b/core/java/android/os/HwRemoteBinder.java @@ -63,4 +63,9 @@ public class HwRemoteBinder implements IHwBinder { } private long mNativeContext; + + @Override + public final native boolean equals(Object other); + @Override + public final native int hashCode(); } diff --git a/core/java/android/os/TokenWatcher.java b/core/java/android/os/TokenWatcher.java index 9b3a2d689693..00333dad1a17 100644 --- a/core/java/android/os/TokenWatcher.java +++ b/core/java/android/os/TokenWatcher.java @@ -16,17 +16,23 @@ package android.os; +import android.util.Log; + import java.io.PrintWriter; import java.util.ArrayList; -import java.util.WeakHashMap; import java.util.Set; -import android.util.Log; +import java.util.WeakHashMap; /** - * Helper class that helps you use IBinder objects as reference counted - * tokens. IBinders make good tokens because we find out when they are - * removed + * A TokenWatcher watches a collection of {@link IBinder}s. IBinders are added + * to the collection by calling {@link #acquire}, and removed by calling {@link + * #release}. IBinders are also implicitly removed when they become weakly + * reachable. Each IBinder may be added at most once. * + * The {@link #acquired} method is invoked by posting to the specified handler + * whenever the size of the watched collection becomes nonzero. The {@link + * #released} method is invoked on the specified handler whenever the size of + * the watched collection becomes zero. */ public abstract class TokenWatcher { @@ -59,15 +65,23 @@ public abstract class TokenWatcher * Record that this token has been acquired. When acquire is called, and * the current count is 0, the acquired method is called on the given * handler. - * - * @param token An IBinder object. If this token has already been acquired, - * no action is taken. + * + * Note that the same {@code token} can only be acquired once. If this + * {@code token} has already been acquired, no action is taken. The first + * subsequent call to {@link #release} will release this {@code token} + * immediately. + * + * @param token An IBinder object. * @param tag A string used by the {@link #dump} method for debugging, * to see who has references. */ public void acquire(IBinder token, String tag) { synchronized (mTokens) { + if (mTokens.containsKey(token)) { + return; + } + // explicitly checked to avoid bogus sendNotification calls because // of the WeakHashMap and the GC int oldSize = mTokens.size(); diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 81ea191462f0..36e4c1c61f0e 100644 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -27,9 +27,6 @@ interface IMediaContainerService { PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, String abiOverride); ObbInfo getObbInfo(String filename); - long calculateDirectorySize(String directory); - /** Return file system stats: [0] is total bytes, [1] is available bytes */ - long[] getFileSystemStats(String path); void clearDirectory(String directory); long calculateInstalledSize(String packagePath, boolean isForwardLocked, String abiOverride); } diff --git a/core/java/com/android/internal/net/OWNERS b/core/java/com/android/internal/net/OWNERS index 7cb32ff793e7..e2064a8099ae 100644 --- a/core/java/com/android/internal/net/OWNERS +++ b/core/java/com/android/internal/net/OWNERS @@ -2,5 +2,5 @@ set noparent ek@google.com hugobenichi@google.com -jsharkey@google.com +jsharkey@android.com lorenzo@google.com diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 6d8b81170e21..3cda820eb276 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -546,13 +546,14 @@ public class ZygoteInit { for (String classPathElement : classPathElements) { // System server is fully AOTed and never profiled // for profile guided compilation. - // TODO: Make this configurable between INTERPRET_ONLY, SPEED, SPACE and EVERYTHING? + String systemServerFilter = SystemProperties.get( + "dalvik.vm.systemservercompilerfilter", "speed"); int dexoptNeeded; try { dexoptNeeded = DexFile.getDexOptNeeded( - classPathElement, instructionSet, "speed", - false /* newProfile */, false /* downgrade */); + classPathElement, instructionSet, systemServerFilter, + null /* classLoaderContext */, false /* newProfile */, false /* downgrade */); } catch (FileNotFoundException ignored) { // Do not add to the classpath. Log.w(TAG, "Missing classpath element for system server: " + classPathElement); @@ -570,13 +571,13 @@ public class ZygoteInit { final String packageName = "*"; final String outputPath = null; final int dexFlags = 0; - final String compilerFilter = "speed"; + final String compilerFilter = systemServerFilter; final String uuid = StorageManager.UUID_PRIVATE_INTERNAL; final String seInfo = null; try { installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName, instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter, - uuid, sharedLibraries, seInfo); + uuid, sharedLibraries, seInfo, false /* downgrade */); } catch (RemoteException | ServiceSpecificException e) { // Ignore (but log), we need this on the classpath for fallback mode. Log.w(TAG, "Failed compiling classpath element for system server: " diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index 8d9630fe5654..e5ad1f47d37d 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -804,7 +804,7 @@ public class StateMachine { /** State that processed the message */ State msgProcessedState = null; - if (mIsConstructionCompleted) { + if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) { /** Normal path */ msgProcessedState = processMsg(msg); } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD) diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index c4f22eeb8b95..08d952791ef6 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -296,7 +296,8 @@ static jobject JHwBinder_native_getService( JNIEnv *env, jclass /* clazzObj */, jstring ifaceNameObj, - jstring serviceNameObj) { + jstring serviceNameObj, + jboolean retry) { using ::android::hidl::base::V1_0::IBase; using ::android::hardware::details::getRawServiceInternal; @@ -319,8 +320,7 @@ static jobject JHwBinder_native_getService( serviceName = str.c_str(); } - // TODO(b/67981006): true /* retry */ - sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */); + sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, retry /* retry */, false /* getStub */); sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret); if (service == NULL) { @@ -360,7 +360,7 @@ static JNINativeMethod gMethods[] = { { "registerService", "(Ljava/lang/String;)V", (void *)JHwBinder_native_registerService }, - { "getService", "(Ljava/lang/String;Ljava/lang/String;)L" PACKAGE_PATH "/IHwBinder;", + { "getService", "(Ljava/lang/String;Ljava/lang/String;Z)L" PACKAGE_PATH "/IHwBinder;", (void *)JHwBinder_native_getService }, { "configureRpcThreadpool", "(JZ)V", diff --git a/core/jni/android_os_HwBlob.cpp b/core/jni/android_os_HwBlob.cpp index 40d49b7662d9..bb916d2431c5 100644 --- a/core/jni/android_os_HwBlob.cpp +++ b/core/jni/android_os_HwBlob.cpp @@ -26,6 +26,7 @@ #include <android_runtime/AndroidRuntime.h> #include <hidl/Status.h> #include <nativehelper/ScopedLocalRef.h> +#include <nativehelper/ScopedPrimitiveArray.h> #include "core_jni_helpers.h" @@ -60,12 +61,12 @@ sp<JHwBlob> JHwBlob::SetNativeContext( JNIEnv *env, jobject thiz, const sp<JHwBlob> &context) { sp<JHwBlob> old = (JHwBlob *)env->GetLongField(thiz, gFields.contextID); - if (context != NULL) { - context->incStrong(NULL /* id */); + if (context != nullptr) { + context->incStrong(nullptr /* id */); } - if (old != NULL) { - old->decStrong(NULL /* id */); + if (old != nullptr) { + old->decStrong(nullptr /* id */); } env->SetLongField(thiz, gFields.contextID, (long)context.get()); @@ -150,6 +151,10 @@ const void *JHwBlob::data() const { return mBuffer; } +void *JHwBlob::data() { + return mBuffer; +} + size_t JHwBlob::size() const { return mSize; } @@ -242,8 +247,8 @@ using namespace android; static void releaseNativeContext(void *nativeContext) { sp<JHwBlob> parcel = (JHwBlob *)nativeContext; - if (parcel != NULL) { - parcel->decStrong(NULL /* id */); + if (parcel != nullptr) { + parcel->decStrong(nullptr /* id */); } } @@ -313,6 +318,82 @@ static jstring JHwBlob_native_getString( return env->NewStringUTF(s->c_str()); } +#define DEFINE_BLOB_ARRAY_COPIER(Suffix,Type,NewType) \ +static void JHwBlob_native_copyTo ## Suffix ## Array( \ + JNIEnv *env, \ + jobject thiz, \ + jlong offset, \ + Type ## Array array, \ + jint size) { \ + if (array == nullptr) { \ + jniThrowException(env, "java/lang/NullPointerException", nullptr); \ + return; \ + } \ + \ + if (env->GetArrayLength(array) < size) { \ + signalExceptionForError(env, BAD_VALUE); \ + return; \ + } \ + \ + sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); \ + \ + if ((offset + size * sizeof(Type)) > blob->size()) { \ + signalExceptionForError(env, -ERANGE); \ + return; \ + } \ + \ + env->Set ## NewType ## ArrayRegion( \ + array, \ + 0 /* start */, \ + size, \ + reinterpret_cast<const Type *>( \ + static_cast<const uint8_t *>(blob->data()) + offset)); \ +} + +DEFINE_BLOB_ARRAY_COPIER(Int8,jbyte,Byte) +DEFINE_BLOB_ARRAY_COPIER(Int16,jshort,Short) +DEFINE_BLOB_ARRAY_COPIER(Int32,jint,Int) +DEFINE_BLOB_ARRAY_COPIER(Int64,jlong,Long) +DEFINE_BLOB_ARRAY_COPIER(Float,jfloat,Float) +DEFINE_BLOB_ARRAY_COPIER(Double,jdouble,Double) + +static void JHwBlob_native_copyToBoolArray( + JNIEnv *env, + jobject thiz, + jlong offset, + jbooleanArray array, + jint size) { + if (array == nullptr) { + jniThrowException(env, "java/lang/NullPointerException", nullptr); + return; + } + + if (env->GetArrayLength(array) < size) { + signalExceptionForError(env, BAD_VALUE); + return; + } + + sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); + + if ((offset + size * sizeof(bool)) > blob->size()) { + signalExceptionForError(env, -ERANGE); + return; + } + + const bool *src = + reinterpret_cast<const bool *>( + static_cast<const uint8_t *>(blob->data()) + offset); + + jboolean *dst = env->GetBooleanArrayElements(array, nullptr /* isCopy */); + + for (jint i = 0; i < size; ++i) { + dst[i] = src[i]; + } + + env->ReleaseBooleanArrayElements(array, dst, 0 /* mode */); + dst = nullptr; +} + #define DEFINE_BLOB_PUTTER(Suffix,Type) \ static void JHwBlob_native_put ## Suffix( \ JNIEnv *env, jobject thiz, jlong offset, Type x) { \ @@ -375,6 +456,59 @@ static void JHwBlob_native_putString( blob->putBlob(offset + hidl_string::kOffsetOfBuffer, subBlob); } +#define DEFINE_BLOB_ARRAY_PUTTER(Suffix,Type,NewType) \ +static void JHwBlob_native_put ## Suffix ## Array( \ + JNIEnv *env, jobject thiz, jlong offset, Type ## Array array) { \ + Scoped ## NewType ## ArrayRO autoArray(env, array); \ + \ + if (array == nullptr) { \ + /* NullpointerException already pending */ \ + return; \ + } \ + \ + sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); \ + \ + status_t err = blob->write( \ + offset, autoArray.get(), autoArray.size() * sizeof(Type)); \ + \ + if (err != OK) { \ + signalExceptionForError(env, err); \ + } \ +} + +DEFINE_BLOB_ARRAY_PUTTER(Int8,jbyte,Byte) +DEFINE_BLOB_ARRAY_PUTTER(Int16,jshort,Short) +DEFINE_BLOB_ARRAY_PUTTER(Int32,jint,Int) +DEFINE_BLOB_ARRAY_PUTTER(Int64,jlong,Long) +DEFINE_BLOB_ARRAY_PUTTER(Float,jfloat,Float) +DEFINE_BLOB_ARRAY_PUTTER(Double,jdouble,Double) + +static void JHwBlob_native_putBoolArray( + JNIEnv *env, jobject thiz, jlong offset, jbooleanArray array) { + ScopedBooleanArrayRO autoArray(env, array); + + if (array == nullptr) { + /* NullpointerException already pending */ + return; + } + + sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); + + if ((offset + autoArray.size() * sizeof(bool)) > blob->size()) { + signalExceptionForError(env, -ERANGE); + return; + } + + const jboolean *src = autoArray.get(); + + bool *dst = reinterpret_cast<bool *>( + static_cast<uint8_t *>(blob->data()) + offset); + + for (size_t i = 0; i < autoArray.size(); ++i) { + dst[i] = src[i]; + } +} + static void JHwBlob_native_putBlob( JNIEnv *env, jobject thiz, jlong offset, jobject blobObj) { if (blobObj == nullptr) { @@ -413,6 +547,14 @@ static JNINativeMethod gMethods[] = { { "getDouble", "(J)D", (void *)JHwBlob_native_getDouble }, { "getString", "(J)Ljava/lang/String;", (void *)JHwBlob_native_getString }, + { "copyToBoolArray", "(J[ZI)V", (void *)JHwBlob_native_copyToBoolArray }, + { "copyToInt8Array", "(J[BI)V", (void *)JHwBlob_native_copyToInt8Array }, + { "copyToInt16Array", "(J[SI)V", (void *)JHwBlob_native_copyToInt16Array }, + { "copyToInt32Array", "(J[II)V", (void *)JHwBlob_native_copyToInt32Array }, + { "copyToInt64Array", "(J[JI)V", (void *)JHwBlob_native_copyToInt64Array }, + { "copyToFloatArray", "(J[FI)V", (void *)JHwBlob_native_copyToFloatArray }, + { "copyToDoubleArray", "(J[DI)V", (void *)JHwBlob_native_copyToDoubleArray }, + { "putBool", "(JZ)V", (void *)JHwBlob_native_putBool }, { "putInt8", "(JB)V", (void *)JHwBlob_native_putInt8 }, { "putInt16", "(JS)V", (void *)JHwBlob_native_putInt16 }, @@ -422,6 +564,14 @@ static JNINativeMethod gMethods[] = { { "putDouble", "(JD)V", (void *)JHwBlob_native_putDouble }, { "putString", "(JLjava/lang/String;)V", (void *)JHwBlob_native_putString }, + { "putBoolArray", "(J[Z)V", (void *)JHwBlob_native_putBoolArray }, + { "putInt8Array", "(J[B)V", (void *)JHwBlob_native_putInt8Array }, + { "putInt16Array", "(J[S)V", (void *)JHwBlob_native_putInt16Array }, + { "putInt32Array", "(J[I)V", (void *)JHwBlob_native_putInt32Array }, + { "putInt64Array", "(J[J)V", (void *)JHwBlob_native_putInt64Array }, + { "putFloatArray", "(J[F)V", (void *)JHwBlob_native_putFloatArray }, + { "putDoubleArray", "(J[D)V", (void *)JHwBlob_native_putDoubleArray }, + { "putBlob", "(JL" PACKAGE_PATH "/HwBlob;)V", (void *)JHwBlob_native_putBlob }, diff --git a/core/jni/android_os_HwBlob.h b/core/jni/android_os_HwBlob.h index 39393cb76c23..6b1db639ac19 100644 --- a/core/jni/android_os_HwBlob.h +++ b/core/jni/android_os_HwBlob.h @@ -50,6 +50,8 @@ struct JHwBlob : public RefBase { size_t offset, const android::hardware::hidl_string **s) const; const void *data() const; + void *data(); + size_t size() const; status_t putBlob(size_t offset, const sp<JHwBlob> &blob); diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp index cf59a56a13dc..ca5e1e45dcdc 100644 --- a/core/jni/android_os_HwRemoteBinder.cpp +++ b/core/jni/android_os_HwRemoteBinder.cpp @@ -22,9 +22,13 @@ #include "android_os_HwParcel.h" -#include <nativehelper/JNIHelp.h> +#include <android/hidl/base/1.0/IBase.h> +#include <android/hidl/base/1.0/BpHwBase.h> +#include <android/hidl/base/1.0/BnHwBase.h> #include <android_runtime/AndroidRuntime.h> #include <hidl/Status.h> +#include <hidl/HidlTransportSupport.h> +#include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> #include <nativehelper/ScopedLocalRef.h> @@ -413,6 +417,44 @@ static jboolean JHwRemoteBinder_unlinkToDeath(JNIEnv* env, jobject thiz, return res; } +static sp<hidl::base::V1_0::IBase> toIBase(JNIEnv* env, jclass hwRemoteBinderClazz, jobject jbinder) +{ + if (jbinder == nullptr) { + return nullptr; + } + if (!env->IsInstanceOf(jbinder, hwRemoteBinderClazz)) { + return nullptr; + } + sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, jbinder); + sp<hardware::IBinder> cbinder = context->getBinder(); + return hardware::fromBinder<hidl::base::V1_0::IBase, hidl::base::V1_0::BpHwBase, + hidl::base::V1_0::BnHwBase>(cbinder); +} + +// equals iff other is also a non-null android.os.HwRemoteBinder object +// and getBinder() returns the same object. +// In particular, if other is an android.os.HwBinder object (for stubs) then +// it returns false. +static jboolean JHwRemoteBinder_equals(JNIEnv* env, jobject thiz, jobject other) +{ + if (env->IsSameObject(thiz, other)) { + return true; + } + if (other == NULL) { + return false; + } + + ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH)); + + return hardware::interfacesEqual(toIBase(env, clazz.get(), thiz), toIBase(env, clazz.get(), other)); +} + +static jint JHwRemoteBinder_hashCode(JNIEnv* env, jobject thiz) { + jlong longHash = reinterpret_cast<jlong>( + JHwRemoteBinder::GetNativeContext(env, thiz)->getBinder().get()); + return static_cast<jint>(longHash ^ (longHash >> 32)); // See Long.hashCode() +} + static JNINativeMethod gMethods[] = { { "native_init", "()J", (void *)JHwRemoteBinder_native_init }, @@ -430,6 +472,11 @@ static JNINativeMethod gMethods[] = { {"unlinkToDeath", "(Landroid/os/IHwBinder$DeathRecipient;)Z", (void*)JHwRemoteBinder_unlinkToDeath}, + + {"equals", "(Ljava/lang/Object;)Z", + (void*)JHwRemoteBinder_equals}, + + {"hashCode", "()I", (void*)JHwRemoteBinder_hashCode}, }; namespace android { diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 7908c9d2b609..560c38486bb9 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -20,6 +20,7 @@ #include "android_os_Parcel.h" #include "android_util_Binder.h" +#include <atomic> #include <fcntl.h> #include <inttypes.h> #include <stdio.h> @@ -96,14 +97,11 @@ static struct binderproxy_offsets_t { // Class state. jclass mClass; - jmethodID mConstructor; + jmethodID mGetInstance; jmethodID mSendDeathNotice; // Object state. - jfieldID mObject; - jfieldID mSelf; - jfieldID mOrgue; - + jfieldID mNativeData; // Field holds native pointer to BinderProxyNativeData. } gBinderProxyOffsets; static struct class_offsets_t @@ -144,20 +142,46 @@ static struct thread_dispatch_offsets_t // **************************************************************************** // **************************************************************************** -static volatile int32_t gNumRefsCreated = 0; -static volatile int32_t gNumProxyRefs = 0; -static volatile int32_t gNumLocalRefs = 0; -static volatile int32_t gNumDeathRefs = 0; +static constexpr int32_t PROXY_WARN_INTERVAL = 5000; +static constexpr uint32_t GC_INTERVAL = 1000; + +// Protected by gProxyLock. We warn if this gets too large. +static int32_t gNumProxies = 0; +static int32_t gProxiesWarned = 0; -static void incRefsCreated(JNIEnv* env) +// Number of GlobalRefs held by JavaBBinders. +static std::atomic<uint32_t> gNumLocalRefsCreated(0); +static std::atomic<uint32_t> gNumLocalRefsDeleted(0); +// Number of GlobalRefs held by JavaDeathRecipients. +static std::atomic<uint32_t> gNumDeathRefsCreated(0); +static std::atomic<uint32_t> gNumDeathRefsDeleted(0); + +// We collected after creating this many refs. +static std::atomic<uint32_t> gCollectedAtRefs(0); + +// Garbage collect if we've allocated at least GC_INTERVAL refs since the last time. +// TODO: Consider removing this completely. We should no longer be generating GlobalRefs +// that are reclaimed as a result of GC action. +__attribute__((no_sanitize("unsigned-integer-overflow"))) +static void gcIfManyNewRefs(JNIEnv* env) { - int old = android_atomic_inc(&gNumRefsCreated); - if (old == 200) { - android_atomic_and(0, &gNumRefsCreated); - env->CallStaticVoidMethod(gBinderInternalOffsets.mClass, - gBinderInternalOffsets.mForceGc); + uint32_t totalRefs = gNumLocalRefsCreated.load(std::memory_order_relaxed) + + gNumDeathRefsCreated.load(std::memory_order_relaxed); + uint32_t collectedAtRefs = gCollectedAtRefs.load(memory_order_relaxed); + // A bound on the number of threads that can have incremented gNum...RefsCreated before the + // following check is executed. Effectively a bound on #threads. Almost any value will do. + static constexpr uint32_t MAX_RACING = 100000; + + if (totalRefs - (collectedAtRefs + GC_INTERVAL) /* modular arithmetic! */ < MAX_RACING) { + // Recently passed next GC interval. + if (gCollectedAtRefs.compare_exchange_strong(collectedAtRefs, + collectedAtRefs + GC_INTERVAL, std::memory_order_relaxed)) { + ALOGV("Binder forcing GC at %u created refs", totalRefs); + env->CallStaticVoidMethod(gBinderInternalOffsets.mClass, + gBinderInternalOffsets.mForceGc); + } // otherwise somebody else beat us to it. } else { - ALOGV("Now have %d binder ops", old); + ALOGV("Now have %d binder ops", totalRefs - collectedAtRefs); } } @@ -267,12 +291,12 @@ class JavaBBinderHolder; class JavaBBinder : public BBinder { public: - JavaBBinder(JNIEnv* env, jobject object) + JavaBBinder(JNIEnv* env, jobject /* Java Binder */ object) : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)) { ALOGV("Creating JavaBBinder %p\n", this); - android_atomic_inc(&gNumLocalRefs); - incRefsCreated(env); + gNumLocalRefsCreated.fetch_add(1, std::memory_order_relaxed); + gcIfManyNewRefs(env); } bool checkSubclass(const void* subclassID) const @@ -289,7 +313,7 @@ protected: virtual ~JavaBBinder() { ALOGV("Destroying JavaBBinder %p\n", this); - android_atomic_dec(&gNumLocalRefs); + gNumLocalRefsDeleted.fetch_add(1, memory_order_relaxed); JNIEnv* env = javavm_to_jnienv(mVM); env->DeleteGlobalRef(mObject); } @@ -351,12 +375,12 @@ protected: private: JavaVM* const mVM; - jobject const mObject; + jobject const mObject; // GlobalRef to Java Binder }; // ---------------------------------------------------------------------------- -class JavaBBinderHolder : public RefBase +class JavaBBinderHolder { public: sp<JavaBBinder> get(JNIEnv* env, jobject obj) @@ -421,8 +445,8 @@ public: LOGDEATH("Adding JDR %p to DRL %p", this, list.get()); list->add(this); - android_atomic_inc(&gNumDeathRefs); - incRefsCreated(env); + gNumDeathRefsCreated.fetch_add(1, std::memory_order_relaxed); + gcIfManyNewRefs(env); } void binderDied(const wp<IBinder>& who) @@ -502,7 +526,7 @@ protected: virtual ~JavaDeathRecipient() { //ALOGI("Removing death ref: recipient=%p\n", mObject); - android_atomic_dec(&gNumDeathRefs); + gNumDeathRefsDeleted.fetch_add(1, std::memory_order_relaxed); JNIEnv* env = javavm_to_jnienv(mVM); if (mObject != NULL) { env->DeleteGlobalRef(mObject); @@ -514,7 +538,7 @@ protected: private: JavaVM* const mVM; jobject mObject; // Initial strong ref to Java-side DeathRecipient. Cleared on binderDied(). - jweak mObjectWeak; // weak ref to the same Java-side DeathRecipient after binderDied(). + jweak mObjectWeak; // Weak ref to the same Java-side DeathRecipient after binderDied(). wp<DeathRecipientList> mList; }; @@ -579,21 +603,40 @@ Mutex& DeathRecipientList::lock() { namespace android { -static void proxy_cleanup(const void* id, void* obj, void* cleanupCookie) -{ - android_atomic_dec(&gNumProxyRefs); - JNIEnv* env = javavm_to_jnienv((JavaVM*)cleanupCookie); - env->DeleteGlobalRef((jobject)obj); +// We aggregate native pointer fields for BinderProxy in a single object to allow +// management with a single NativeAllocationRegistry, and to reduce the number of JNI +// Java field accesses. This costs us some extra indirections here. +struct BinderProxyNativeData { + // Both fields are constant and not null once javaObjectForIBinder returns this as + // part of a BinderProxy. + + // The native IBinder proxied by this BinderProxy. + sp<IBinder> mObject; + + // Death recipients for mObject. Reference counted only because DeathRecipients + // hold a weak reference that can be temporarily promoted. + sp<DeathRecipientList> mOrgue; // Death recipients for mObject. +}; + +BinderProxyNativeData* getBPNativeData(JNIEnv* env, jobject obj) { + return (BinderProxyNativeData *) env->GetLongField(obj, gBinderProxyOffsets.mNativeData); } static Mutex gProxyLock; +// We may cache a single BinderProxyNativeData node to avoid repeat allocation. +// All fields are null. Protected by gProxyLock. +static BinderProxyNativeData *gNativeDataCache; + +// If the argument is a JavaBBinder, return the Java object that was used to create it. +// Otherwise return a BinderProxy for the IBinder. If a previous call was passed the +// same IBinder, and the original BinderProxy is still alive, return the same BinderProxy. jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val) { if (val == NULL) return NULL; if (val->checkSubclass(&gBinderOffsets)) { - // One of our own! + // It's a JavaBBinder created by ibinderForJavaObject. Already has Java object. jobject object = static_cast<JavaBBinder*>(val.get())->object(); LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object); return object; @@ -603,42 +646,31 @@ jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val) // looking/creation/destruction of Java proxies for native Binder proxies. AutoMutex _l(gProxyLock); - // Someone else's... do we know about it? - jobject object = (jobject)val->findObject(&gBinderProxyOffsets); - if (object != NULL) { - jobject res = jniGetReferent(env, object); - if (res != NULL) { - ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res); - return res; - } - LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get()); - android_atomic_dec(&gNumProxyRefs); - val->detachObject(&gBinderProxyOffsets); - env->DeleteGlobalRef(object); + BinderProxyNativeData* nativeData = gNativeDataCache; + if (nativeData == nullptr) { + nativeData = new BinderProxyNativeData(); } - - object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor); - if (object != NULL) { - LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object); - // The proxy holds a reference to the native object. - env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get()); - val->incStrong((void*)javaObjectForIBinder); - - // The native object needs to hold a weak reference back to the - // proxy, so we can retrieve the same proxy if it is still active. - jobject refObject = env->NewGlobalRef( - env->GetObjectField(object, gBinderProxyOffsets.mSelf)); - val->attachObject(&gBinderProxyOffsets, refObject, - jnienv_to_javavm(env), proxy_cleanup); - - // Also remember the death recipients registered on this proxy - sp<DeathRecipientList> drl = new DeathRecipientList; - drl->incStrong((void*)javaObjectForIBinder); - env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast<jlong>(drl.get())); - - // Note that a new object reference has been created. - android_atomic_inc(&gNumProxyRefs); - incRefsCreated(env); + // gNativeDataCache is now logically empty. + jobject object = env->CallStaticObjectMethod(gBinderProxyOffsets.mClass, + gBinderProxyOffsets.mGetInstance, (jlong) nativeData, (jlong) val.get()); + if (env->ExceptionCheck()) { + gNativeDataCache = nativeData; + return NULL; + } + BinderProxyNativeData* actualNativeData = getBPNativeData(env, object); + if (actualNativeData == nativeData) { + // New BinderProxy; we still have exclusive access. + nativeData->mOrgue = new DeathRecipientList; + nativeData->mObject = val; + gNativeDataCache = nullptr; + ++gNumProxies; + if (++gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) { + ALOGW("Unexpectedly many live BinderProxies: %d\n", gNumProxies); + gProxiesWarned = gNumProxies; + } + } else { + // nativeData wasn't used. Reuse it the next time. + gNativeDataCache = nativeData; } return object; @@ -648,15 +680,16 @@ sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj) { if (obj == NULL) return NULL; + // Instance of Binder? if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) { JavaBBinderHolder* jbh = (JavaBBinderHolder*) env->GetLongField(obj, gBinderOffsets.mObject); - return jbh != NULL ? jbh->get(env, obj) : NULL; + return jbh->get(env, obj); } + // Instance of BinderProxy? if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) { - return (IBinder*) - env->GetLongField(obj, gBinderProxyOffsets.mObject); + return getBPNativeData(env, obj)->mObject; } ALOGW("ibinderForJavaObject: %p is not a Binder object", obj); @@ -849,35 +882,21 @@ static void android_os_Binder_flushPendingCommands(JNIEnv* env, jobject clazz) IPCThreadState::self()->flushCommands(); } -static void android_os_Binder_init(JNIEnv* env, jobject obj) +static jlong android_os_Binder_getNativeBBinderHolder(JNIEnv* env, jobject clazz) { JavaBBinderHolder* jbh = new JavaBBinderHolder(); - if (jbh == NULL) { - jniThrowException(env, "java/lang/OutOfMemoryError", NULL); - return; - } - ALOGV("Java Binder %p: acquiring first ref on holder %p", obj, jbh); - jbh->incStrong((void*)android_os_Binder_init); - env->SetLongField(obj, gBinderOffsets.mObject, (jlong)jbh); + return (jlong) jbh; } -static void android_os_Binder_destroyBinder(JNIEnv* env, jobject obj) +static void Binder_destroy(void* rawJbh) { - JavaBBinderHolder* jbh = (JavaBBinderHolder*) - env->GetLongField(obj, gBinderOffsets.mObject); - if (jbh != NULL) { - env->SetLongField(obj, gBinderOffsets.mObject, 0); - ALOGV("Java Binder %p: removing ref on holder %p", obj, jbh); - jbh->decStrong((void*)android_os_Binder_init); - } else { - // Encountering an uninitialized binder is harmless. All it means is that - // the Binder was only partially initialized when its finalizer ran and called - // destroyBinder(). The Binder could be partially initialized for several reasons. - // For example, a Binder subclass constructor might have thrown an exception before - // it could delegate to its superclass's constructor. Consequently init() would - // not have been called and the holder pointer would remain NULL. - ALOGV("Java Binder %p: ignoring uninitialized binder", obj); - } + JavaBBinderHolder* jbh = (JavaBBinderHolder*) rawJbh; + ALOGV("Java Binder: deleting holder %p", jbh); + delete jbh; +} + +JNIEXPORT jlong JNICALL android_os_Binder_getNativeFinalizer(JNIEnv*, jclass) { + return (jlong) Binder_destroy; } static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject clazz) @@ -896,8 +915,8 @@ static const JNINativeMethod gBinderMethods[] = { { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy }, { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy }, { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands }, - { "init", "()V", (void*)android_os_Binder_init }, - { "destroyBinder", "()V", (void*)android_os_Binder_destroyBinder }, + { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder }, + { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer }, { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable } }; @@ -924,17 +943,18 @@ namespace android { jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz) { - return gNumLocalRefs; + return gNumLocalRefsCreated - gNumLocalRefsDeleted; } jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz) { - return gNumProxyRefs; + AutoMutex _l(gProxyLock); + return gNumProxies; } jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz) { - return gNumDeathRefs; + return gNumDeathRefsCreated - gNumDeathRefsDeleted; } } @@ -969,8 +989,8 @@ static void android_os_BinderInternal_setMaxThreads(JNIEnv* env, static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz) { - ALOGV("Gc has executed, clearing binder ops"); - android_atomic_and(0, &gNumRefsCreated); + ALOGV("Gc has executed, updating Refs count at GC"); + gCollectedAtRefs = gNumLocalRefsCreated + gNumDeathRefsCreated; } // ---------------------------------------------------------------------------- @@ -1004,8 +1024,7 @@ static int int_register_android_os_BinderInternal(JNIEnv* env) static jboolean android_os_BinderProxy_pingBinder(JNIEnv* env, jobject obj) { - IBinder* target = (IBinder*) - env->GetLongField(obj, gBinderProxyOffsets.mObject); + IBinder* target = getBPNativeData(env, obj)->mObject.get(); if (target == NULL) { return JNI_FALSE; } @@ -1015,7 +1034,7 @@ static jboolean android_os_BinderProxy_pingBinder(JNIEnv* env, jobject obj) static jstring android_os_BinderProxy_getInterfaceDescriptor(JNIEnv* env, jobject obj) { - IBinder* target = (IBinder*) env->GetLongField(obj, gBinderProxyOffsets.mObject); + IBinder* target = getBPNativeData(env, obj)->mObject.get(); if (target != NULL) { const String16& desc = target->getInterfaceDescriptor(); return env->NewString(reinterpret_cast<const jchar*>(desc.string()), @@ -1028,8 +1047,7 @@ static jstring android_os_BinderProxy_getInterfaceDescriptor(JNIEnv* env, jobjec static jboolean android_os_BinderProxy_isBinderAlive(JNIEnv* env, jobject obj) { - IBinder* target = (IBinder*) - env->GetLongField(obj, gBinderProxyOffsets.mObject); + IBinder* target = getBPNativeData(env, obj)->mObject.get(); if (target == NULL) { return JNI_FALSE; } @@ -1151,8 +1169,7 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, return JNI_FALSE; } - IBinder* target = (IBinder*) - env->GetLongField(obj, gBinderProxyOffsets.mObject); + IBinder* target = getBPNativeData(env, obj)->mObject.get(); if (target == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!"); return JNI_FALSE; @@ -1202,18 +1219,13 @@ static void android_os_BinderProxy_linkToDeath(JNIEnv* env, jobject obj, return; } - IBinder* target = (IBinder*) - env->GetLongField(obj, gBinderProxyOffsets.mObject); - if (target == NULL) { - ALOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient); - assert(false); - } + BinderProxyNativeData *nd = getBPNativeData(env, obj); + IBinder* target = nd->mObject.get(); LOGDEATH("linkToDeath: binder=%p recipient=%p\n", target, recipient); if (!target->localBinder()) { - DeathRecipientList* list = (DeathRecipientList*) - env->GetLongField(obj, gBinderProxyOffsets.mOrgue); + DeathRecipientList* list = nd->mOrgue.get(); sp<JavaDeathRecipient> jdr = new JavaDeathRecipient(env, recipient, list); status_t err = target->linkToDeath(jdr, NULL, flags); if (err != NO_ERROR) { @@ -1234,8 +1246,8 @@ static jboolean android_os_BinderProxy_unlinkToDeath(JNIEnv* env, jobject obj, return res; } - IBinder* target = (IBinder*) - env->GetLongField(obj, gBinderProxyOffsets.mObject); + BinderProxyNativeData* nd = getBPNativeData(env, obj); + IBinder* target = nd->mObject.get(); if (target == NULL) { ALOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient); return JNI_FALSE; @@ -1247,8 +1259,7 @@ static jboolean android_os_BinderProxy_unlinkToDeath(JNIEnv* env, jobject obj, status_t err = NAME_NOT_FOUND; // If we find the matching recipient, proceed to unlink using that - DeathRecipientList* list = (DeathRecipientList*) - env->GetLongField(obj, gBinderProxyOffsets.mOrgue); + DeathRecipientList* list = nd->mOrgue.get(); sp<JavaDeathRecipient> origJDR = list->find(recipient); LOGDEATH(" unlink found list %p and JDR %p", list, origJDR.get()); if (origJDR != NULL) { @@ -1274,25 +1285,21 @@ static jboolean android_os_BinderProxy_unlinkToDeath(JNIEnv* env, jobject obj, return res; } -static void android_os_BinderProxy_destroy(JNIEnv* env, jobject obj) +static void BinderProxy_destroy(void* rawNativeData) { // Don't race with construction/initialization AutoMutex _l(gProxyLock); - IBinder* b = (IBinder*) - env->GetLongField(obj, gBinderProxyOffsets.mObject); - DeathRecipientList* drl = (DeathRecipientList*) - env->GetLongField(obj, gBinderProxyOffsets.mOrgue); - - LOGDEATH("Destroying BinderProxy %p: binder=%p drl=%p\n", obj, b, drl); - if (b != nullptr) { - env->SetLongField(obj, gBinderProxyOffsets.mObject, 0); - env->SetLongField(obj, gBinderProxyOffsets.mOrgue, 0); - drl->decStrong((void*)javaObjectForIBinder); - b->decStrong((void*)javaObjectForIBinder); - } - + BinderProxyNativeData * nativeData = (BinderProxyNativeData *) rawNativeData; + LOGDEATH("Destroying BinderProxy: binder=%p drl=%p\n", + nativeData->mObject.get(), nativeData->mOrgue.get()); + delete nativeData; IPCThreadState::self()->flushCommands(); + --gNumProxies; +} + +JNIEXPORT jlong JNICALL android_os_BinderProxy_getNativeFinalizer(JNIEnv*, jclass) { + return (jlong) BinderProxy_destroy; } // ---------------------------------------------------------------------------- @@ -1305,7 +1312,7 @@ static const JNINativeMethod gBinderProxyMethods[] = { {"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact}, {"linkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, {"unlinkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, - {"destroy", "()V", (void*)android_os_BinderProxy_destroy}, + {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer}, }; const char* const kBinderProxyPathName = "android/os/BinderProxy"; @@ -1317,14 +1324,11 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) clazz = FindClassOrDie(env, kBinderProxyPathName); gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz); - gBinderProxyOffsets.mConstructor = GetMethodIDOrDie(env, clazz, "<init>", "()V"); + gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance", + "(JJ)Landroid/os/BinderProxy;"); gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice", "(Landroid/os/IBinder$DeathRecipient;)V"); - - gBinderProxyOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J"); - gBinderProxyOffsets.mSelf = GetFieldIDOrDie(env, clazz, "mSelf", - "Ljava/lang/ref/WeakReference;"); - gBinderProxyOffsets.mOrgue = GetFieldIDOrDie(env, clazz, "mOrgue", "J"); + gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J"); clazz = FindClassOrDie(env, "java/lang/Class"); gClassOffsets.mGetName = GetMethodIDOrDie(env, clazz, "getName", "()Ljava/lang/String;"); diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 40fb6e83e94d..e291ea6cc856 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2402,7 +2402,13 @@ <bool name="config_networkSamplingWakesDevice">true</bool> - <string-array translatable="false" name="config_cdma_home_system" /> + <!-- Home (non-roaming) values for CDMA roaming indicator. + Carriers can override this table by resource overlay. If not, + the default values come from 3GPP2 C.R1001 table + 8.1-1. Enhanced Roaming Indicator Number Assignments --> + <string-array translatable="false" name="config_cdma_home_system"> + <item>1</item> + </string-array> <!--From SmsMessage--> <!--Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java index eb2a5165dc4f..76aa93f7e8be 100644 --- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java @@ -24,6 +24,7 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.SystemClock; +import android.os.test.TestLooper; import android.test.suitebuilder.annotation.Suppress; import com.android.internal.util.State; @@ -343,6 +344,100 @@ public class StateMachineTest extends TestCase { } /** + * Tests {@link StateMachine#quitNow()} immediately after {@link StateMachine#start()}. + */ + class StateMachineQuitNowAfterStartTest extends StateMachine { + Collection<LogRec> mLogRecs; + + StateMachineQuitNowAfterStartTest(String name, Looper looper) { + super(name, looper); + mThisSm = this; + setDbg(DBG); + + // Setup state machine with 1 state + addState(mS1); + + // Set the initial state + setInitialState(mS1); + } + + @Override + public void onQuitting() { + tlog("onQuitting"); + addLogRec(ON_QUITTING); + mLogRecs = mThisSm.copyLogRecs(); + synchronized (mThisSm) { + mThisSm.notifyAll(); + } + } + + class S1 extends State { + @Override + public void enter() { + tlog("S1.enter"); + addLogRec(ENTER); + } + @Override + public void exit() { + tlog("S1.exit"); + addLogRec(EXIT); + } + @Override + public boolean processMessage(Message message) { + switch(message.what) { + // Sleep and assume the other messages will be queued up. + case TEST_CMD_1: { + tlog("TEST_CMD_1"); + sleep(500); + break; + } + default: { + tlog("default what=" + message.what); + break; + } + } + return HANDLED; + } + } + + private StateMachineQuitNowAfterStartTest mThisSm; + private S1 mS1 = new S1(); + } + + /** + * When quitNow() is called immediately after start(), the QUIT_CMD gets processed + * before the INIT_CMD. This test ensures that the StateMachine can gracefully handle + * this sequencing of messages (QUIT before INIT). + */ + @SmallTest + public void testStateMachineQuitNowAfterStart() throws Exception { + if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger(); + + TestLooper testLooper = new TestLooper(); + StateMachineQuitNowAfterStartTest smQuitNowAfterStartTest = + new StateMachineQuitNowAfterStartTest( + "smQuitNowAfterStartTest", testLooper.getLooper()); + smQuitNowAfterStartTest.start(); + smQuitNowAfterStartTest.quitNow(); + if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart E"); + + testLooper.dispatchAll(); + dumpLogRecs(smQuitNowAfterStartTest.mLogRecs); + assertEquals(2, smQuitNowAfterStartTest.mLogRecs.size()); + + LogRec lr; + Iterator<LogRec> itr = smQuitNowAfterStartTest.mLogRecs.iterator(); + lr = itr.next(); + assertEquals(EXIT, lr.getInfo()); + assertEquals(smQuitNowAfterStartTest.mS1, lr.getState()); + + lr = itr.next(); + assertEquals(ON_QUITTING, lr.getInfo()); + + if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart X"); + } + + /** * Test enter/exit can use transitionTo */ class StateMachineEnterExitTransitionToTest extends StateMachine { diff --git a/data/etc/OWNERS b/data/etc/OWNERS new file mode 100644 index 000000000000..f7a3e1ae8959 --- /dev/null +++ b/data/etc/OWNERS @@ -0,0 +1,7 @@ +per-file privapp-permissions-platform.xml = bpoiesz@google.com +per-file privapp-permissions-platform.xml = fkupolov@google.com +per-file privapp-permissions-platform.xml = hackbod@android.com +per-file privapp-permissions-platform.xml = jsharkey@android.com +per-file privapp-permissions-platform.xml = svetoslavganov@google.com +per-file privapp-permissions-platform.xml = toddke@google.com +per-file privapp-permissions-platform.xml = yamasani@google.com diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp index 5abfc8ee917e..5d243da2097c 100644 --- a/libs/androidfw/ZipUtils.cpp +++ b/libs/androidfw/ZipUtils.cpp @@ -20,10 +20,11 @@ #define LOG_TAG "ziputil" +#include "android-base/file.h" #include <androidfw/ZipUtils.h> -#include <androidfw/ZipFileRO.h> #include <utils/Log.h> #include <utils/Compat.h> +#include <ziparchive/zip_archive.h> #include <stdlib.h> #include <string.h> @@ -33,211 +34,121 @@ using namespace android; -static inline unsigned long get4LE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); -} - - -static const unsigned long kReadBufSize = 32768; - -/* - * Utility function that expands zip/gzip "deflate" compressed data - * into a buffer. - * - * (This is a clone of the previous function, but it takes a FILE* instead - * of an fd. We could pass fileno(fd) to the above, but we can run into - * trouble when "fp" has a different notion of what fd's file position is.) - * - * "fp" is an open file positioned at the start of the "deflate" data - * "buf" must hold at least "uncompressedLen" bytes. - */ -/*static*/ template<typename T> bool inflateToBuffer(T& reader, void* buf, - long uncompressedLen, long compressedLen) -{ - bool result = false; - - z_stream zstream; - int zerr; - unsigned long compRemaining; - - assert(uncompressedLen >= 0); - assert(compressedLen >= 0); - - compRemaining = compressedLen; - - /* - * Initialize the zlib stream. - */ - memset(&zstream, 0, sizeof(zstream)); - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.opaque = Z_NULL; - zstream.next_in = NULL; - zstream.avail_in = 0; - zstream.next_out = (Bytef*) buf; - zstream.avail_out = uncompressedLen; - zstream.data_type = Z_UNKNOWN; - - /* - * Use the undocumented "negative window bits" feature to tell zlib - * that there's no zlib header waiting for it. - */ - zerr = inflateInit2(&zstream, -MAX_WBITS); - if (zerr != Z_OK) { - if (zerr == Z_VERSION_ERROR) { - ALOGE("Installed zlib is not compatible with linked version (%s)\n", - ZLIB_VERSION); - } else { - ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); - } - goto bail; +// TODO: This can go away once the only remaining usage in aapt goes away. +class FileReader : public zip_archive::Reader { + public: + FileReader(FILE* fp) : Reader(), mFp(fp), mCurrentOffset(0) { } - /* - * Loop while we have data. - */ - do { - unsigned long getSize; - - /* read as much as we can */ - if (zstream.avail_in == 0) { - getSize = (compRemaining > kReadBufSize) ? - kReadBufSize : compRemaining; - ALOGV("+++ reading %ld bytes (%ld left)\n", - getSize, compRemaining); - - unsigned char* nextBuffer = NULL; - const unsigned long nextSize = reader.read(&nextBuffer, getSize); - - if (nextSize < getSize || nextBuffer == NULL) { - ALOGD("inflate read failed (%ld vs %ld)\n", nextSize, getSize); - goto z_bail; + bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const { + // Data is usually requested sequentially, so this helps avoid pointless + // fseeks every time we perform a read. There's an impedence mismatch + // here because the original API was designed around pread and pwrite. + if (offset != mCurrentOffset) { + if (fseek(mFp, offset, SEEK_SET) != 0) { + return false; } - compRemaining -= nextSize; - - zstream.next_in = nextBuffer; - zstream.avail_in = nextSize; + mCurrentOffset = offset; } - /* uncompress the data */ - zerr = inflate(&zstream, Z_NO_FLUSH); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - ALOGD("zlib inflate call failed (zerr=%d)\n", zerr); - goto z_bail; + size_t read = fread(buf, 1, len, mFp); + if (read != len) { + return false; } - /* output buffer holds all, so no need to write the output */ - } while (zerr == Z_OK); - - assert(zerr == Z_STREAM_END); /* other errors should've been caught */ - - if ((long) zstream.total_out != uncompressedLen) { - ALOGW("Size mismatch on inflated file (%ld vs %ld)\n", - zstream.total_out, uncompressedLen); - goto z_bail; + mCurrentOffset += read; + return true; } - // success! - result = true; - -z_bail: - inflateEnd(&zstream); /* free up any allocated structures */ - -bail: - return result; -} - -class FileReader { -public: - explicit FileReader(FILE* fp) : - mFp(fp), mReadBuf(new unsigned char[kReadBufSize]) - { - } + private: + FILE* mFp; + mutable uint32_t mCurrentOffset; +}; - ~FileReader() { - delete[] mReadBuf; - } +class FdReader : public zip_archive::Reader { + public: + explicit FdReader(int fd) : mFd(fd) { + } - long read(unsigned char** nextBuffer, long readSize) const { - *nextBuffer = mReadBuf; - return fread(mReadBuf, 1, readSize, mFp); - } + bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const { + return android::base::ReadFullyAtOffset(mFd, buf, len, static_cast<off_t>(offset)); + } - FILE* mFp; - unsigned char* mReadBuf; + private: + const int mFd; }; -class FdReader { -public: - explicit FdReader(int fd) : - mFd(fd), mReadBuf(new unsigned char[kReadBufSize]) - { - } +class BufferReader : public zip_archive::Reader { + public: + BufferReader(const void* input, size_t inputSize) : Reader(), + mInput(reinterpret_cast<const uint8_t*>(input)), + mInputSize(inputSize) { + } - ~FdReader() { - delete[] mReadBuf; - } + bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const { + if (offset + len > mInputSize) { + return false; + } - long read(unsigned char** nextBuffer, long readSize) const { - *nextBuffer = mReadBuf; - return TEMP_FAILURE_RETRY(::read(mFd, mReadBuf, readSize)); - } + memcpy(buf, mInput + offset, len); + return true; + } - int mFd; - unsigned char* mReadBuf; + private: + const uint8_t* mInput; + const size_t mInputSize; }; -class BufferReader { -public: - BufferReader(void* input, size_t inputSize) : - mInput(reinterpret_cast<unsigned char*>(input)), - mInputSize(inputSize), - mBufferReturned(false) - { +class BufferWriter : public zip_archive::Writer { + public: + BufferWriter(void* output, size_t outputSize) : Writer(), + mOutput(reinterpret_cast<uint8_t*>(output)), mOutputSize(outputSize), mBytesWritten(0) { } - long read(unsigned char** nextBuffer, long /*readSize*/) { - if (!mBufferReturned) { - mBufferReturned = true; - *nextBuffer = mInput; - return mInputSize; + bool Append(uint8_t* buf, size_t bufSize) override { + if (mBytesWritten + bufSize > mOutputSize) { + return false; } - *nextBuffer = NULL; - return 0; + memcpy(mOutput + mBytesWritten, buf, bufSize); + mBytesWritten += bufSize; + return true; } - unsigned char* mInput; - const size_t mInputSize; - bool mBufferReturned; + private: + uint8_t* const mOutput; + const size_t mOutputSize; + size_t mBytesWritten; }; /*static*/ bool ZipUtils::inflateToBuffer(FILE* fp, void* buf, long uncompressedLen, long compressedLen) { FileReader reader(fp); - return ::inflateToBuffer<FileReader>(reader, buf, - uncompressedLen, compressedLen); + BufferWriter writer(buf, uncompressedLen); + return (zip_archive::Inflate(reader, compressedLen, uncompressedLen, &writer, nullptr) == 0); } /*static*/ bool ZipUtils::inflateToBuffer(int fd, void* buf, long uncompressedLen, long compressedLen) { FdReader reader(fd); - return ::inflateToBuffer<FdReader>(reader, buf, - uncompressedLen, compressedLen); + BufferWriter writer(buf, uncompressedLen); + return (zip_archive::Inflate(reader, compressedLen, uncompressedLen, &writer, nullptr) == 0); } -/*static*/ bool ZipUtils::inflateToBuffer(void* in, void* buf, +/*static*/ bool ZipUtils::inflateToBuffer(const void* in, void* buf, long uncompressedLen, long compressedLen) { BufferReader reader(in, compressedLen); - return ::inflateToBuffer<BufferReader>(reader, buf, - uncompressedLen, compressedLen); + BufferWriter writer(buf, uncompressedLen); + return (zip_archive::Inflate(reader, compressedLen, uncompressedLen, &writer, nullptr) == 0); } - +static inline unsigned long get4LE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); +} /* * Look at the contents of a gzip archive. We want to know where the @@ -275,7 +186,7 @@ public: /* quick sanity checks */ if (method == EOF || flags == EOF) return false; - if (method != ZipFileRO::kCompressDeflated) + if (method != kCompressDeflated) return false; /* skip over 4 bytes of mod time, 1 byte XFL, 1 byte OS */ diff --git a/libs/androidfw/include/androidfw/ZipUtils.h b/libs/androidfw/include/androidfw/ZipUtils.h index 55575d774522..4d35e992cc89 100644 --- a/libs/androidfw/include/androidfw/ZipUtils.h +++ b/libs/androidfw/include/androidfw/ZipUtils.h @@ -40,7 +40,7 @@ public: long compressedLen); static bool inflateToBuffer(int fd, void* buf, long uncompressedLen, long compressedLen); - static bool inflateToBuffer(void *in, void* buf, long uncompressedLen, + static bool inflateToBuffer(const void *in, void* buf, long uncompressedLen, long compressedLen); /* diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 934787774637..3800e6f7e509 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -213,27 +213,6 @@ public class DefaultContainerService extends IntentService { } @Override - public long calculateDirectorySize(String path) throws RemoteException { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path)); - if (dir.exists() && dir.isDirectory()) { - final String targetPath = dir.getAbsolutePath(); - return MeasurementUtils.measureDirectory(targetPath); - } else { - return 0L; - } - } - - @Override - public long[] getFileSystemStats(String path) { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - final File file = new File(path); - return new long[] { file.getTotalSpace(), file.getUsableSpace() }; - } - - @Override public void clearDirectory(String path) throws RemoteException { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java index 7bda2314f4c4..3299cb2d1221 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -32,7 +32,7 @@ import java.util.List; /** * PanProfile handles Bluetooth PAN profile (NAP and PANU). */ -public final class PanProfile implements LocalBluetoothProfile { +public class PanProfile implements LocalBluetoothProfile { private static final String TAG = "PanProfile"; private static boolean V = true; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index e2ebbeb411fb..9ac08f97c8f0 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -51,7 +51,7 @@ public class CachedBluetoothDeviceTest { @Mock private A2dpProfile mA2dpProfile; @Mock - private HidProfile mHidProfile; + private PanProfile mPanProfile; @Mock private BluetoothDevice mDevice; private CachedBluetoothDevice mCachedDevice; @@ -65,7 +65,7 @@ public class CachedBluetoothDeviceTest { when(mAdapter.getBluetoothState()).thenReturn(BluetoothAdapter.STATE_ON); when(mHfpProfile.isProfileReady()).thenReturn(true); when(mA2dpProfile.isProfileReady()).thenReturn(true); - when(mHidProfile.isProfileReady()).thenReturn(true); + when(mPanProfile.isProfileReady()).thenReturn(true); mCachedDevice = spy( new CachedBluetoothDevice(mContext, mAdapter, mProfileManager, mDevice)); doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel(); @@ -83,37 +83,37 @@ public class CachedBluetoothDeviceTest { @Test public void testGetConnectionSummary_testSingleProfileConnectDisconnect() { // Test without battery level - // Set HID profile to be connected and test connection state summary - mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED); + // Set PAN profile to be connected and test connection state summary + mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString( R.string.bluetooth_connected)); - // Set HID profile to be disconnected and test connection state summary - mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED); + // Set PAN profile to be disconnected and test connection state summary + mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED); assertThat(mCachedDevice.getConnectionSummary()).isNull(); // Test with battery level mBatteryLevel = 10; - // Set HID profile to be connected and test connection state summary - mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED); + // Set PAN profile to be connected and test connection state summary + mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString( R.string.bluetooth_connected_battery_level, com.android.settingslib.Utils.formatPercentage(mBatteryLevel))); - // Set HID profile to be disconnected and test connection state summary - mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED); + // Set PAN profile to be disconnected and test connection state summary + mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED); assertThat(mCachedDevice.getConnectionSummary()).isNull(); // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; - // Set HID profile to be connected and test connection state summary - mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED); + // Set PAN profile to be connected and test connection state summary + mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString( R.string.bluetooth_connected)); - // Set HID profile to be disconnected and test connection state summary - mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED); + // Set PAN profile to be disconnected and test connection state summary + mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED); assertThat(mCachedDevice.getConnectionSummary()).isNull(); } @@ -121,10 +121,10 @@ public class CachedBluetoothDeviceTest { public void testGetConnectionSummary_testMultipleProfileConnectDisconnect() { mBatteryLevel = 10; - // Set HFP, A2DP and HID profile to be connected and test connection state summary + // Set HFP, A2DP and PAN profile to be connected and test connection state summary mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED); mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED); - mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString( R.string.bluetooth_connected_battery_level, com.android.settingslib.Utils.formatPercentage(mBatteryLevel))); @@ -149,7 +149,7 @@ public class CachedBluetoothDeviceTest { com.android.settingslib.Utils.formatPercentage(mBatteryLevel))); // Disconnect all profiles and test connection state summary - mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED); + mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED); assertThat(mCachedDevice.getConnectionSummary()).isNull(); } } diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index 35a947777dd1..b138df086253 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -63,7 +63,8 @@ android:layout_width="18dp" android:layout_height="match_parent" android:src="@drawable/qs_dual_tile_caret" - android:tint="?android:attr/textColorPrimary" /> + android:tint="?android:attr/textColorPrimary" + android:visibility="gone" /> </LinearLayout> <TextView diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto index 8dd35affa05d..82a341567f18 100644 --- a/proto/src/ipconnectivity.proto +++ b/proto/src/ipconnectivity.proto @@ -50,9 +50,10 @@ message Pair { optional int32 value = 2; }; -// An event record when the system default network disconnects or the system -// switches to a new default network. -// Next tag: 10. +// An event recorded when the system default network disconnects or the system +// switches to a new default network. An event is also recorded to cover gaps +// without a default network. +// Next tag: 12 message DefaultNetworkEvent { // Reason why this network stopped being the default. @@ -72,26 +73,34 @@ message DefaultNetworkEvent { }; // Duration in milliseconds when this network was the default. - // Since version 4 + // Since P. optional int64 default_network_duration_ms = 5; - // Duration in milliseconds without a default network before this network - // became the default. - // Since version 4 - optional int64 no_default_network_duration_ms = 6; + // Duration in milliseconds when this default network Internet access was + // validated. This field is equal to 0 for DefaultNetworkEvents representing + // lack of a default network. + // Since P. + optional int64 validation_duration_ms = 11; // Network score of this network when it became the default network. - // Since version 4 + // Or 0 if this event represents a period without a default network. + // Since P. optional int64 initial_score = 7; // Network score of this network when it stopped being the default network. - // Since version 4 + // Or 0 if this event represents a period without a default network. + // Since P. optional int64 final_score = 8; // Best available information about IP support of this default network. - // Since version 4 + // Or NONE if this event represents a period without a default network. + // Since P. optional IPSupport ip_support = 9; + // LinkLayer of the previous default network. Ignores any previous period + // without a default network. + // Since P + optional LinkLayer previous_default_network_link_layer = 10; // Deprecated fields @@ -112,6 +121,11 @@ message DefaultNetworkEvent { // TRANSPORT_* constants as defined in NetworkCapabilities. // Deprecated since version 3. Replaced by top-level transports field. repeated int32 transport_types = 4 [deprecated = true]; + + // Duration in milliseconds without a default network. This field is non-zero + // only for DefaultNetworkEvents representing lack of a default network. + // Since P. + optional int64 no_default_network_duration_ms = 6 [deprecated = true]; }; // Logs IpReachabilityMonitor probe events and NUD_FAILED events. diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 25c96d14e9ad..664c2abdffd3 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -29,7 +29,10 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; + import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.Nullable; @@ -71,6 +74,7 @@ import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.UidRange; import android.net.Uri; +import android.net.VpnService; import android.net.metrics.IpConnectivityLog; import android.net.metrics.NetworkEvent; import android.net.util.MultinetworkPolicyTracker; @@ -2109,9 +2113,14 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID); final boolean wasValidated = nai.lastValidated; + final boolean wasDefault = isDefaultNetwork(nai); if (DBG) log(nai.name() + " validation " + (valid ? "passed" : "failed") + (msg.obj == null ? "" : " with redirect to " + (String)msg.obj)); if (valid != nai.lastValidated) { + if (wasDefault) { + metricsLogger().defaultNetworkMetrics().logDefaultNetworkValidity( + SystemClock.elapsedRealtime(), valid); + } final int oldScore = nai.getCurrentScore(); nai.lastValidated = valid; nai.everValidated |= valid; @@ -2283,7 +2292,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // Let rematchAllNetworksAndRequests() below record a new default network event // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence // whose timestamps tell how long it takes to recover a default network. - metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai); + long now = SystemClock.elapsedRealtime(); + metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(now, null, nai); } notifyIfacesChangedForNetworkStats(); // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied @@ -4664,10 +4674,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - final NetworkCapabilities prevNc = nai.networkCapabilities; + final NetworkCapabilities prevNc; synchronized (nai) { + prevNc = nai.networkCapabilities; nai.networkCapabilities = networkCapabilities; } + if (nai.getCurrentScore() == oldScore && networkCapabilities.equalRequestableCapabilities(prevNc)) { // If the requestable capabilities haven't changed, and the score hasn't changed, then @@ -4681,6 +4693,28 @@ public class ConnectivityService extends IConnectivityManager.Stub rematchAllNetworksAndRequests(nai, oldScore); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } + + // 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); + final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) != + networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING); + if (meteredChanged || roamingChanged) { + notifyIfacesChangedForNetworkStats(); + } + } + + if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) { + // Tell VPNs about updated capabilities, since they may need to + // bubble those changes through. + synchronized (mVpns) { + for (int i = 0; i < mVpns.size(); i++) { + final Vpn vpn = mVpns.valueAt(i); + vpn.updateCapabilities(); + } + } + } } public void handleUpdateLinkProperties(NetworkAgentInfo nai, LinkProperties newLp) { @@ -5013,7 +5047,7 @@ public class ConnectivityService extends IConnectivityManager.Stub makeDefault(newNetwork); // Log 0 -> X and Y -> X default network transitions, where X is the new default. metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent( - newNetwork, oldDefaultNetwork); + now, newNetwork, oldDefaultNetwork); // Have a new default network, release the transition wakelock in scheduleReleaseNetworkTransitionWakelock(); } @@ -5214,14 +5248,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } notifyLockdownVpn(networkAgent); - if (oldInfo != null && oldInfo.getState() == state) { - if (oldInfo.isRoaming() != newInfo.isRoaming()) { - if (VDBG) log("roaming status changed, notifying NetworkStatsService"); - notifyIfacesChangedForNetworkStats(); - } else if (VDBG) log("ignoring duplicate network state non-change"); - // In either case, no further work should be needed. - return; - } if (DBG) { log(networkAgent.name() + " EVENT_NETWORK_INFO_CHANGED, going from " + (oldInfo == null ? "null" : oldInfo.getState()) + diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 192fa3a2bb38..6339c9e47b1e 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -1447,7 +1447,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED); mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter); - buildInputMethodListLocked(true /* resetDefaultEnabledIme */); + final String defaultImiId = mSettings.getSelectedInputMethod(); + final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); + buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */); resetDefaultImeLocked(mContext); updateFromSettingsLocked(true); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index a139ac4f3f68..1154fbe60973 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -944,13 +944,13 @@ public class IpSecService extends IIpSecService.Stub { (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0, spi, (auth != null) ? auth.getName() : "", - (auth != null) ? auth.getKey() : null, + (auth != null) ? auth.getKey() : new byte[] {}, (auth != null) ? auth.getTruncationLengthBits() : 0, (crypt != null) ? crypt.getName() : "", - (crypt != null) ? crypt.getKey() : null, + (crypt != null) ? crypt.getKey() : new byte[] {}, (crypt != null) ? crypt.getTruncationLengthBits() : 0, (authCrypt != null) ? authCrypt.getName() : "", - (authCrypt != null) ? authCrypt.getKey() : null, + (authCrypt != null) ? authCrypt.getKey() : new byte[] {}, (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0, encapType, encapLocalPort, diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index f82eb1f3e991..86ce12eee32b 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -5068,8 +5068,16 @@ public class AccountManagerService logStatement.bindLong(4, callingUid); logStatement.bindString(5, tableName); logStatement.bindLong(6, userDebugDbInsertionPoint); - logStatement.execute(); - logStatement.clearBindings(); + try { + logStatement.execute(); + } catch (IllegalStateException e) { + // Guard against crash, DB can already be closed + // since this statement is executed on a handler thread + Slog.w(TAG, "Failed to insert a log record. accountId=" + accountId + + " action=" + action + " tableName=" + tableName + " Error: " + e); + } finally { + logStatement.clearBindings(); + } } } diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java index 8981db11f7f8..28c358582dbe 100644 --- a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java +++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java @@ -18,9 +18,11 @@ package com.android.server.connectivity; import android.net.LinkProperties; import android.net.metrics.DefaultNetworkEvent; -import android.net.metrics.IpConnectivityLog; +import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.BitUtils; +import com.android.internal.util.RingBuffer; import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent; import java.io.PrintWriter; @@ -35,19 +37,49 @@ public class DefaultNetworkMetrics { private static final int ROLLING_LOG_SIZE = 64; + public final long creationTimeMs = SystemClock.elapsedRealtime(); + // Event buffer used for metrics upload. The buffer is cleared when events are collected. @GuardedBy("this") private final List<DefaultNetworkEvent> mEvents = new ArrayList<>(); + // Rolling event buffer used for dumpsys and bugreports. + @GuardedBy("this") + private final RingBuffer<DefaultNetworkEvent> mEventsLog = + new RingBuffer(DefaultNetworkEvent.class, ROLLING_LOG_SIZE); + + // Information about the current status of the default network. + @GuardedBy("this") + private DefaultNetworkEvent mCurrentDefaultNetwork; + @GuardedBy("this") + private boolean mIsCurrentlyValid; + @GuardedBy("this") + private long mLastValidationTimeMs; + // Transport information about the last default network. + @GuardedBy("this") + private int mLastTransports; + + public DefaultNetworkMetrics() { + newDefaultNetwork(creationTimeMs, null); + } + public synchronized void listEvents(PrintWriter pw) { + pw.println("default network events:"); long localTimeMs = System.currentTimeMillis(); - for (DefaultNetworkEvent ev : mEvents) { - pw.println(ev); + long timeMs = SystemClock.elapsedRealtime(); + for (DefaultNetworkEvent ev : mEventsLog.toArray()) { + printEvent(localTimeMs, pw, ev); + } + mCurrentDefaultNetwork.updateDuration(timeMs); + if (mIsCurrentlyValid) { + updateValidationTime(timeMs); + mLastValidationTimeMs = timeMs; } + printEvent(localTimeMs, pw, mCurrentDefaultNetwork); } public synchronized void listEventsAsProto(PrintWriter pw) { - for (DefaultNetworkEvent ev : mEvents) { + for (DefaultNetworkEvent ev : mEventsLog.toArray()) { pw.print(IpConnectivityEventBuilder.toProto(ev)); } } @@ -59,20 +91,75 @@ public class DefaultNetworkMetrics { mEvents.clear(); } - public synchronized void logDefaultNetworkEvent( - NetworkAgentInfo newNai, NetworkAgentInfo prevNai) { - DefaultNetworkEvent ev = new DefaultNetworkEvent(); - if (newNai != null) { - ev.netId = newNai.network().netId; - ev.transportTypes = newNai.networkCapabilities.getTransportTypes(); + public synchronized void logDefaultNetworkValidity(long timeMs, boolean isValid) { + if (!isValid && mIsCurrentlyValid) { + mIsCurrentlyValid = false; + updateValidationTime(timeMs); } - if (prevNai != null) { - ev.prevNetId = prevNai.network().netId; - final LinkProperties lp = prevNai.linkProperties; - ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute(); - ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute(); + + if (isValid && !mIsCurrentlyValid) { + mIsCurrentlyValid = true; + mLastValidationTimeMs = timeMs; } + } + + private void updateValidationTime(long timeMs) { + mCurrentDefaultNetwork.validatedMs += timeMs - mLastValidationTimeMs; + } + + public synchronized void logDefaultNetworkEvent( + long timeMs, NetworkAgentInfo newNai, NetworkAgentInfo oldNai) { + logCurrentDefaultNetwork(timeMs, oldNai); + newDefaultNetwork(timeMs, newNai); + } + private void logCurrentDefaultNetwork(long timeMs, NetworkAgentInfo oldNai) { + DefaultNetworkEvent ev = mCurrentDefaultNetwork; + ev.updateDuration(timeMs); + ev.previousTransports = mLastTransports; + // oldNai is null if the system had no default network before the transition. + if (oldNai != null) { + // The system acquired a new default network. + fillLinkInfo(ev, oldNai); + ev.finalScore = oldNai.getCurrentScore(); + ev.validatedMs = ev.durationMs; + } + // Only change transport of the previous default network if the event currently logged + // corresponds to an existing default network, and not to the absence of a default network. + // This allows to log pairs of transports for successive default networks regardless of + // whether or not the system experienced a period without any default network. + if (ev.transports != 0) { + mLastTransports = ev.transports; + } mEvents.add(ev); + mEventsLog.append(ev); + } + + private void newDefaultNetwork(long timeMs, NetworkAgentInfo newNai) { + DefaultNetworkEvent ev = new DefaultNetworkEvent(timeMs); + ev.durationMs = timeMs; + // newNai is null if the system has no default network after the transition. + if (newNai != null) { + fillLinkInfo(ev, newNai); + ev.initialScore = newNai.getCurrentScore(); + if (newNai.lastValidated) { + mIsCurrentlyValid = true; + mLastValidationTimeMs = timeMs; + } + } + mCurrentDefaultNetwork = ev; + } + + private static void fillLinkInfo(DefaultNetworkEvent ev, NetworkAgentInfo nai) { + LinkProperties lp = nai.linkProperties; + ev.netId = nai.network().netId; + ev.transports |= BitUtils.packBits(nai.networkCapabilities.getTransportTypes()); + ev.ipv4 |= lp.hasIPv4Address() && lp.hasIPv4DefaultRoute(); + ev.ipv6 |= lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute(); + } + + private static void printEvent(long localTimeMs, PrintWriter pw, DefaultNetworkEvent ev) { + long localCreationTimeMs = localTimeMs - ev.durationMs; + pw.println(String.format("%tT.%tL: %s", localCreationTimeMs, localCreationTimeMs, ev)); } } diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index 3d71ecb50d9f..a011692be37c 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -25,6 +25,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; +import android.net.ConnectivityManager; import android.net.ConnectivityMetricsEvent; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; @@ -135,11 +136,17 @@ final public class IpConnectivityEventBuilder { public static IpConnectivityEvent toProto(DefaultNetworkEvent in) { IpConnectivityLogClass.DefaultNetworkEvent ev = new IpConnectivityLogClass.DefaultNetworkEvent(); - ev.networkId = netIdOf(in.netId); - ev.previousNetworkId = netIdOf(in.prevNetId); - ev.transportTypes = in.transportTypes; - ev.previousNetworkIpSupport = ipSupportOf(in); - final IpConnectivityEvent out = buildEvent(in.netId, 0, null); + ev.finalScore = in.finalScore; + ev.initialScore = in.initialScore; + ev.ipSupport = ipSupportOf(in); + ev.defaultNetworkDurationMs = in.durationMs; + ev.validationDurationMs = in.validatedMs; + ev.previousDefaultNetworkLinkLayer = transportsToLinkLayer(in.previousTransports); + final IpConnectivityEvent out = buildEvent(in.netId, in.transports, null); + if (in.transports == 0) { + // Set link layer to NONE for events representing the absence of a default network. + out.linkLayer = IpConnectivityLogClass.NONE; + } out.setDefaultNetworkEvent(ev); return out; } @@ -321,13 +328,13 @@ final public class IpConnectivityEventBuilder { } private static int ipSupportOf(DefaultNetworkEvent in) { - if (in.prevIPv4 && in.prevIPv6) { + if (in.ipv4 && in.ipv6) { return IpConnectivityLogClass.DefaultNetworkEvent.DUAL; } - if (in.prevIPv6) { + if (in.ipv6) { return IpConnectivityLogClass.DefaultNetworkEvent.IPV6; } - if (in.prevIPv4) { + if (in.ipv4) { return IpConnectivityLogClass.DefaultNetworkEvent.IPV4; } return IpConnectivityLogClass.DefaultNetworkEvent.NONE; diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index 24217e6eef0b..5cc390a02b86 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -45,6 +45,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.function.ToIntFunction; @@ -214,86 +215,66 @@ final public class IpConnectivityMetrics extends SystemService { } /** - * Clears the event buffer and prints its content as a protobuf serialized byte array + * Clear the event buffer and prints its content as a protobuf serialized byte array * inside a base64 encoded string. */ - private void cmdFlush(FileDescriptor fd, PrintWriter pw, String[] args) { + private void cmdFlush(PrintWriter pw) { pw.print(flushEncodedOutput()); } /** - * Prints the content of the event buffer, either using the events ASCII representation - * or using protobuf text format. + * Print the content of the rolling event buffer in human readable format. + * Also print network dns/connect statistics and recent default network events. */ - private void cmdList(FileDescriptor fd, PrintWriter pw, String[] args) { - final ArrayList<ConnectivityMetricsEvent> events; - synchronized (mLock) { - events = new ArrayList(mBuffer); - } - - if (args.length > 1 && args[1].equals("proto")) { - for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) { - pw.print(ev.toString()); - } - if (mNetdListener != null) { - mNetdListener.listAsProtos(pw); - } - mDefaultNetworkMetrics.listEventsAsProto(pw); - return; - } - + private void cmdList(PrintWriter pw) { + pw.println("metrics events:"); + final List<ConnectivityMetricsEvent> events = getEvents(); for (ConnectivityMetricsEvent ev : events) { pw.println(ev.toString()); } + pw.println(""); if (mNetdListener != null) { mNetdListener.list(pw); } + pw.println(""); mDefaultNetworkMetrics.listEvents(pw); } - /** - * Prints for bug reports the content of the rolling event log and the - * content of Netd event listener. + /* + * Print the content of the rolling event buffer in text proto format. */ - private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) { - final ConnectivityMetricsEvent[] events; - synchronized (mLock) { - events = mEventLog.toArray(); - } - for (ConnectivityMetricsEvent ev : events) { - pw.println(ev.toString()); + private void cmdListAsProto(PrintWriter pw) { + final List<ConnectivityMetricsEvent> events = getEvents(); + for (IpConnectivityEvent ev : IpConnectivityEventBuilder.toProto(events)) { + pw.print(ev.toString()); } if (mNetdListener != null) { - mNetdListener.list(pw); + mNetdListener.listAsProtos(pw); } - mDefaultNetworkMetrics.listEvents(pw); + mDefaultNetworkMetrics.listEventsAsProto(pw); } - private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) { + /* + * Return a copy of metrics events stored in buffer for metrics uploading. + */ + private List<ConnectivityMetricsEvent> getEvents() { synchronized (mLock) { - pw.println("Buffered events: " + mBuffer.size()); - pw.println("Buffer capacity: " + mCapacity); - pw.println("Dropped events: " + mDropped); - } - if (mNetdListener != null) { - mNetdListener.dump(pw); + return Arrays.asList(mEventLog.toArray()); } } - private void cmdDefault(FileDescriptor fd, PrintWriter pw, String[] args) { - if (args.length == 0) { - pw.println("No command"); - return; - } - pw.println("Unknown command " + TextUtils.join(" ", args)); - } - public final class Impl extends IIpConnectivityMetrics.Stub { - static final String CMD_FLUSH = "flush"; - static final String CMD_LIST = "list"; - static final String CMD_STATS = "stats"; - static final String CMD_DUMPSYS = "-a"; // dumpsys.cpp dumps services with "-a" as arguments - static final String CMD_DEFAULT = CMD_STATS; + // Dump and flushes the metrics event buffer in base64 encoded serialized proto output. + static final String CMD_FLUSH = "flush"; + // Dump the rolling buffer of metrics event in human readable proto text format. + static final String CMD_PROTO = "proto"; + // Dump the rolling buffer of metrics event and pretty print events using a human readable + // format. Also print network dns/connect statistics and default network event time series. + static final String CMD_LIST = "list"; + // By default any other argument will fall into the default case which is remapped to the + // "list" command. This includes most notably bug reports collected by dumpsys.cpp with + // the "-a" argument. + static final String CMD_DEFAULT = CMD_LIST; @Override public int logEvent(ConnectivityMetricsEvent event) { @@ -308,19 +289,15 @@ final public class IpConnectivityMetrics extends SystemService { final String cmd = (args.length > 0) ? args[0] : CMD_DEFAULT; switch (cmd) { case CMD_FLUSH: - cmdFlush(fd, pw, args); + cmdFlush(pw); return; - case CMD_DUMPSYS: - cmdDumpsys(fd, pw, args); - return; - case CMD_LIST: - cmdList(fd, pw, args); - return; - case CMD_STATS: - cmdStats(fd, pw, args); + case CMD_PROTO: + cmdListAsProto(pw); return; + case CMD_LIST: // fallthrough default: - cmdDefault(fd, pw, args); + cmdList(pw); + return; } } diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 05c6e69c1895..61b11e18e3cd 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -243,24 +243,21 @@ public class NetdEventListenerService extends INetdEventListener.Stub { mWakeupStats.clear(); } - public synchronized void dump(PrintWriter writer) { - IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println(TAG + ":"); - pw.increaseIndent(); - list(pw); - pw.decreaseIndent(); - } - public synchronized void list(PrintWriter pw) { + pw.println("dns/connect events:"); for (int i = 0; i < mNetworkMetrics.size(); i++) { pw.println(mNetworkMetrics.valueAt(i).connectMetrics); } for (int i = 0; i < mNetworkMetrics.size(); i++) { pw.println(mNetworkMetrics.valueAt(i).dnsMetrics); } + pw.println(""); + pw.println("network statistics:"); for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) { pw.println(s); } + pw.println(""); + pw.println("packet wakeup events:"); for (int i = 0; i < mWakeupStats.size(); i++) { pw.println(mWakeupStats.valueAt(i)); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index a44b18dc2d8c..7715727f6d73 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -18,6 +18,8 @@ package com.android.server.connectivity; import static android.Manifest.permission.BIND_VPN_SERVICE; import static android.net.ConnectivityManager.NETID_UNSET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; @@ -90,6 +92,8 @@ import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnInfo; import com.android.internal.net.VpnProfile; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.util.ArrayUtils; +import com.android.server.ConnectivityService; import com.android.server.DeviceIdleController; import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; @@ -245,10 +249,10 @@ public class Vpn { } mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, ""); - // TODO: Copy metered attribute and bandwidths from physical transport, b/16207332 mNetworkCapabilities = new NetworkCapabilities(); mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN); mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); + updateCapabilities(); loadAlwaysOnPackage(); } @@ -275,6 +279,62 @@ public class Vpn { updateAlwaysOnNotification(detailedState); } + public void updateCapabilities() { + final Network[] underlyingNetworks = (mConfig != null) ? mConfig.underlyingNetworks : null; + updateCapabilities(mContext.getSystemService(ConnectivityManager.class), underlyingNetworks, + mNetworkCapabilities); + + if (mNetworkAgent != null) { + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + } + + @VisibleForTesting + public static void updateCapabilities(ConnectivityManager cm, Network[] underlyingNetworks, + NetworkCapabilities caps) { + int[] transportTypes = new int[] { NetworkCapabilities.TRANSPORT_VPN }; + int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; + int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; + boolean metered = false; + boolean roaming = false; + + if (ArrayUtils.isEmpty(underlyingNetworks)) { + // No idea what the underlying networks are; assume sane defaults + metered = true; + roaming = false; + } else { + for (Network underlying : underlyingNetworks) { + final NetworkCapabilities underlyingCaps = cm.getNetworkCapabilities(underlying); + for (int underlyingType : underlyingCaps.getTransportTypes()) { + transportTypes = ArrayUtils.appendInt(transportTypes, underlyingType); + } + + // When we have multiple networks, we have to assume the + // worst-case link speed and restrictions. + downKbps = NetworkCapabilities.minBandwidth(downKbps, + underlyingCaps.getLinkDownstreamBandwidthKbps()); + upKbps = NetworkCapabilities.minBandwidth(upKbps, + underlyingCaps.getLinkUpstreamBandwidthKbps()); + metered |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_METERED); + roaming |= !underlyingCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING); + } + } + + caps.setTransportTypes(transportTypes); + caps.setLinkDownstreamBandwidthKbps(downKbps); + caps.setLinkUpstreamBandwidthKbps(upKbps); + if (metered) { + caps.removeCapability(NET_CAPABILITY_NOT_METERED); + } else { + caps.addCapability(NET_CAPABILITY_NOT_METERED); + } + if (roaming) { + caps.removeCapability(NET_CAPABILITY_NOT_ROAMING); + } else { + caps.addCapability(NET_CAPABILITY_NOT_ROAMING); + } + } + /** * Chooses whether to force all connections to go though VPN. * @@ -1344,6 +1404,7 @@ public class Vpn { } } } + updateCapabilities(); return true; } diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS index 061fd8da9d60..6b77d8326a39 100644 --- a/services/core/java/com/android/server/net/OWNERS +++ b/services/core/java/com/android/server/net/OWNERS @@ -2,7 +2,7 @@ set noparent ek@google.com hugobenichi@google.com -jsharkey@google.com +jsharkey@android.com lorenzo@google.com satk@google.com silberst@google.com diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 6749afb4fee0..e5c48ccf5442 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; -import android.app.AlarmManager; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; @@ -38,8 +37,10 @@ import android.util.Log; import com.android.server.pm.dex.DexManager; import com.android.server.LocalServices; import com.android.server.PinnerService; +import com.android.server.pm.dex.DexoptOptions; import java.io.File; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.TimeUnit; @@ -73,6 +74,9 @@ public class BackgroundDexOptService extends JobService { // Optimizations should be aborted. No space left on device. private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3; + // Used for calculating space threshold for downgrading unused apps. + private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2; + /** * Set of failed packages remembered across job runs. */ @@ -92,6 +96,9 @@ public class BackgroundDexOptService extends JobService { private final File mDataDir = Environment.getDataDirectory(); + private static final long mDowngradeUnusedAppsThresholdInMillis = + getDowngradeUnusedAppsThresholdInMillis(); + public static void schedule(Context context) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); @@ -211,11 +218,10 @@ public class BackgroundDexOptService extends JobService { // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a // trade-off worth doing to save boot time work. - int result = pm.performDexOptWithStatus(pkg, - /* checkProfiles */ false, + int result = pm.performDexOptWithStatus(new DexoptOptions( + pkg, PackageManagerService.REASON_BOOT, - /* force */ false, - /* bootComplete */ true); + DexoptOptions.DEXOPT_BOOT_COMPLETE)); if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); } @@ -243,7 +249,8 @@ public class BackgroundDexOptService extends JobService { } // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). - private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context) { + private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, + Context context) { Log.i(TAG, "Performing idle optimizations"); // If post-boot update is still running, request that it exits early. mExitPostBootUpdate.set(true); @@ -274,9 +281,16 @@ public class BackgroundDexOptService extends JobService { long lowStorageThreshold, boolean is_for_primary_dex, ArraySet<String> failedPackageNames) { ArraySet<String> updatedPackages = new ArraySet<>(); + Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis); + // Only downgrade apps when space is low on device. + // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean + // up disk before user hits the actual lowStorageThreshold. + final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE * + lowStorageThreshold; + boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade); for (String pkg : pkgs) { int abort_code = abortIdleOptimizations(lowStorageThreshold); - if (abort_code != OPTIMIZE_CONTINUE) { + if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { return abort_code; } @@ -284,30 +298,58 @@ public class BackgroundDexOptService extends JobService { if (failedPackageNames.contains(pkg)) { // Skip previously failing package continue; + } + } + + int reason; + boolean downgrade; + // Downgrade unused packages. + if (unusedPackages.contains(pkg) && shouldDowngrade) { + // This applies for system apps or if packages location is not a directory, i.e. + // monolithic install. + if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) { + // For apps that don't have the oat directory, instead of downgrading, + // remove their compiler artifacts from dalvik cache. + pm.deleteOatArtifactsOfPackage(pkg); + continue; } else { - // Conservatively add package to the list of failing ones in case performDexOpt - // never returns. - failedPackageNames.add(pkg); + reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE; + downgrade = true; } + } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) { + reason = PackageManagerService.REASON_BACKGROUND_DEXOPT; + downgrade = false; + } else { + // can't dexopt because of low space. + continue; + } + + synchronized (failedPackageNames) { + // Conservatively add package to the list of failing ones in case + // performDexOpt never returns. + failedPackageNames.add(pkg); } // Optimize package if needed. Note that there can be no race between // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. boolean success; + int dexoptFlags = + DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | + DexoptOptions.DEXOPT_BOOT_COMPLETE | + (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) | + DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; if (is_for_primary_dex) { - int result = pm.performDexOptWithStatus(pkg, - /* checkProfiles */ true, + int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, PackageManagerService.REASON_BACKGROUND_DEXOPT, - /* force */ false, - /* bootComplete */ true); + dexoptFlags)); success = result != PackageDexOptimizer.DEX_OPT_FAILED; if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); } } else { - success = pm.performDexOptSecondary(pkg, + success = pm.performDexOpt(new DexoptOptions(pkg, PackageManagerService.REASON_BACKGROUND_DEXOPT, - /* force */ false); + dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX)); } if (success) { // Dexopt succeeded, remove package from the list of failing ones. @@ -347,6 +389,16 @@ public class BackgroundDexOptService extends JobService { return OPTIMIZE_CONTINUE; } + // Evaluate whether apps should be downgraded. + private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) { + long usableSpace = mDataDir.getUsableSpace(); + if (usableSpace < lowStorageThresholdForDowngrade) { + return true; + } + + return false; + } + /** * Execute the idle optimizations immediately. */ @@ -415,4 +467,14 @@ public class BackgroundDexOptService extends JobService { pinnerService.update(updatedPackages); } } + + private static long getDowngradeUnusedAppsThresholdInMillis() { + final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days"; + String sysPropValue = SystemProperties.get(sysPropKey); + if (sysPropValue == null || sysPropValue.isEmpty()) { + Log.w(TAG, "SysProp " + sysPropKey + " not set"); + return Long.MAX_VALUE; + } + return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue)); + } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 5c4c0401b63c..210eb1385035 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -56,6 +56,8 @@ public class Installer extends SystemService { public static final int DEXOPT_STORAGE_CE = 1 << 7; /** Indicates that the dex file passed to dexopt in on DE storage. */ public static final int DEXOPT_STORAGE_DE = 1 << 8; + /** Indicates that dexopt is invoked from the background service. */ + public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9; // NOTE: keep in sync with installd public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8; @@ -258,7 +260,7 @@ public class Installer extends SystemService { public long[] getExternalSize(String uuid, int userId, int flags, int[] appIds) throws InstallerException { - if (!checkBeforeRemote()) return new long[4]; + if (!checkBeforeRemote()) return new long[6]; try { return mInstalld.getExternalSize(uuid, userId, flags, appIds); } catch (Exception e) { @@ -279,13 +281,13 @@ public class Installer extends SystemService { public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries, - @Nullable String seInfo) + @Nullable String seInfo, boolean downgrade) throws InstallerException { assertValidInstructionSet(instructionSet); if (!checkBeforeRemote()) return; try { mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath, - dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo); + dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index a43f8afa4d97..73ac05738f0f 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; -import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; import android.annotation.Nullable; import android.content.Context; @@ -30,17 +29,16 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.storage.StorageManager; -import android.text.TextUtils; import android.util.Log; import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.server.pm.Installer.InstallerException; +import com.android.server.pm.dex.DexoptOptions; import java.io.File; import java.io.FileDescriptor; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; @@ -55,7 +53,8 @@ public class OtaDexoptService extends IOtaDexopt.Stub { private final static boolean DEBUG_DEXOPT = true; // The synthetic library dependencies denoting "no checks." - private final static String[] NO_LIBRARIES = new String[] { "&" }; + private final static String[] NO_LIBRARIES = + new String[] { PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK }; // The amount of "available" (free - low threshold) space necessary at the start of an OTA to // not bulk-delete unused apps' odex files. @@ -261,11 +260,12 @@ public class OtaDexoptService extends IOtaDexopt.Stub { public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet, int dexoptNeeded, @Nullable String outputPath, int dexFlags, String compilerFilter, @Nullable String volumeUuid, - @Nullable String sharedLibraries, @Nullable String seInfo) throws InstallerException { + @Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade) + throws InstallerException { final StringBuilder builder = new StringBuilder(); - // The version. Right now it's 2. - builder.append("2 "); + // The version. Right now it's 3. + builder.append("3 "); builder.append("dexopt"); @@ -280,6 +280,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub { encodeParameter(builder, volumeUuid); encodeParameter(builder, sharedLibraries); encodeParameter(builder, seInfo); + encodeParameter(builder, downgrade); commands.add(builder.toString()); } @@ -314,12 +315,18 @@ public class OtaDexoptService extends IOtaDexopt.Stub { libraryDependencies = NO_LIBRARIES; } + optimizer.performDexOpt(pkg, libraryDependencies, - null /* ISAs */, false /* checkProfiles */, - getCompilerFilterForReason(compilationReason), + null /* ISAs */, null /* CompilerStats.PackageStats */, - mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), - true /* bootComplete */); + mPackageManagerService.getDexManager().getPackageUseInfoOrDefault(pkg.packageName), + new DexoptOptions(pkg.packageName, compilationReason, + DexoptOptions.DEXOPT_BOOT_COMPLETE)); + + mPackageManagerService.getDexManager().dexoptSecondaryDex( + new DexoptOptions(pkg.packageName, compilationReason, + DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_BOOT_COMPLETE)); return commands; } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 7e9596adf138..ef015e7e418b 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -19,9 +19,7 @@ package com.android.server.pm; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageParser; -import android.os.Environment; import android.os.FileUtils; import android.os.PowerManager; import android.os.SystemClock; @@ -30,17 +28,21 @@ import android.os.UserHandle; import android.os.WorkSource; import android.util.Log; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.server.pm.Installer.InstallerException; +import com.android.server.pm.dex.DexManager; +import com.android.server.pm.dex.DexoptOptions; +import com.android.server.pm.dex.DexoptUtils; +import com.android.server.pm.dex.PackageDexUsage; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Set; +import java.util.Map; import dalvik.system.DexFile; @@ -52,6 +54,7 @@ import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX; import static com.android.server.pm.Installer.DEXOPT_FORCE; import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE; import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE; +import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; @@ -123,17 +126,16 @@ public class PackageDexOptimizer { * synchronized on {@link #mInstallLock}. */ int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries, - String[] instructionSets, boolean checkProfiles, String targetCompilationFilter, - CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps, - boolean bootComplete) { + String[] instructionSets, CompilerStats.PackageStats packageStats, + PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) { if (!canOptimizePackage(pkg)) { return DEX_OPT_SKIPPED; } synchronized (mInstallLock) { final long acquireTime = acquireWakeLockLI(pkg.applicationInfo.uid); try { - return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles, - targetCompilationFilter, packageStats, isUsedByOtherApps, bootComplete); + return performDexOptLI(pkg, sharedLibraries, instructionSets, + packageStats, packageUseInfo, options); } finally { releaseWakeLockLI(acquireTime); } @@ -146,46 +148,73 @@ public class PackageDexOptimizer { */ @GuardedBy("mInstallLock") private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries, - String[] targetInstructionSets, boolean checkForProfileUpdates, - String targetCompilerFilter, CompilerStats.PackageStats packageStats, - boolean isUsedByOtherApps, boolean bootComplete) { + String[] targetInstructionSets, CompilerStats.PackageStats packageStats, + PackageDexUsage.PackageUseInfo packageUseInfo, DexoptOptions options) { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); final List<String> paths = pkg.getAllCodePaths(); final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo, - targetCompilerFilter, isUsedByOtherApps); - final boolean profileUpdated = checkForProfileUpdates && - isProfileUpdated(pkg, sharedGid, compilerFilter); + // Get the class loader context dependencies. + // For each code path in the package, this array contains the class loader context that + // needs to be passed to dexopt in order to ensure correct optimizations. + boolean[] pathsWithCode = new boolean[paths.size()]; + pathsWithCode[0] = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0; + for (int i = 1; i < paths.size(); i++) { + pathsWithCode[i] = (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) != 0; + } + String[] classLoaderContexts = DexoptUtils.getClassLoaderContexts( + pkg.applicationInfo, sharedLibraries, pathsWithCode); - final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries); - // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. - final int dexoptFlags = getDexFlags(pkg, compilerFilter, bootComplete); - // Get the dependencies of each split in the package. For each code path in the package, - // this array contains the relative paths of each split it depends on, separated by colons. - String[] splitDependencies = getSplitDependencies(pkg); + // Sanity check that we do not call dexopt with inconsistent data. + if (paths.size() != classLoaderContexts.length) { + String[] splitCodePaths = pkg.applicationInfo.getSplitCodePaths(); + throw new IllegalStateException("Inconsistent information " + + "between PackageParser.Package and its ApplicationInfo. " + + "pkg.getAllCodePaths=" + paths + + " pkg.applicationInfo.getBaseCodePath=" + pkg.applicationInfo.getBaseCodePath() + + " pkg.applicationInfo.getSplitCodePaths=" + + (splitCodePaths == null ? "null" : Arrays.toString(splitCodePaths))); + } int result = DEX_OPT_SKIPPED; for (int i = 0; i < paths.size(); i++) { // Skip paths that have no code. - if ((i == 0 && (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) || - (i != 0 && (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) == 0)) { + if (!pathsWithCode[i]) { continue; } + if (classLoaderContexts[i] == null) { + throw new IllegalStateException("Inconsistent information in the " + + "package structure. A split is marked to contain code " + + "but has no dependency listed. Index=" + i + " path=" + paths.get(i)); + } + // Append shared libraries with split dependencies for this split. String path = paths.get(i); - String sharedLibrariesPathWithSplits; - if (sharedLibrariesPath != null && splitDependencies[i] != null) { - sharedLibrariesPathWithSplits = sharedLibrariesPath + ":" + splitDependencies[i]; - } else { - sharedLibrariesPathWithSplits = - splitDependencies[i] != null ? splitDependencies[i] : sharedLibrariesPath; + if (options.getSplitName() != null) { + // We are asked to compile only a specific split. Check that the current path is + // what we are looking for. + if (!options.getSplitName().equals(new File(path).getName())) { + continue; + } } + + final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary() + || packageUseInfo.isUsedByOtherApps(path); + final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo, + options.getCompilerFilter(), isUsedByOtherApps); + final boolean profileUpdated = options.isCheckForProfileUpdates() && + isProfileUpdated(pkg, sharedGid, compilerFilter); + + // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct + // flags. + final int dexoptFlags = getDexFlags(pkg, compilerFilter, options); + for (String dexCodeIsa : dexCodeInstructionSets) { - int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated, - sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats); + int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, + profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid, + packageStats, options.isDowngrade()); // The end result is: // - FAILED if any path failed, // - PERFORMED if at least one path needed compilation, @@ -208,9 +237,10 @@ public class PackageDexOptimizer { */ @GuardedBy("mInstallLock") private int dexOptPath(PackageParser.Package pkg, String path, String isa, - String compilerFilter, boolean profileUpdated, String sharedLibrariesPath, - int dexoptFlags, int uid, CompilerStats.PackageStats packageStats) { - int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated); + String compilerFilter, boolean profileUpdated, String classLoaderContext, + int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) { + int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext, + profileUpdated, downgrade); if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) { return DEX_OPT_SKIPPED; } @@ -223,14 +253,18 @@ public class PackageDexOptimizer { Log.i(TAG, "Running dexopt (dexoptNeeded=" + dexoptNeeded + ") on: " + path + " pkg=" + pkg.applicationInfo.packageName + " isa=" + isa + " dexoptFlags=" + printDexoptFlags(dexoptFlags) - + " target-filter=" + compilerFilter + " oatDir=" + oatDir - + " sharedLibraries=" + sharedLibrariesPath); + + " targetFilter=" + compilerFilter + " oatDir=" + oatDir + + " classLoaderContext=" + classLoaderContext); try { long startTime = System.currentTimeMillis(); + // TODO: Consider adding 2 different APIs for primary and secondary dexopt. + // installd only uses downgrade flag for secondary dex files and ignores it for + // primary dex files. mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags, - compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo); + compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo, + false /* downgrade*/); if (packageStats != null) { long endTime = System.currentTimeMillis(); @@ -257,13 +291,12 @@ public class PackageDexOptimizer { * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though * that seems wasteful. */ - public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas, - String compilerFilter, boolean isUsedByOtherApps) { + public int dexOptSecondaryDexPath(ApplicationInfo info, String path, + PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) { synchronized (mInstallLock) { final long acquireTime = acquireWakeLockLI(info.uid); try { - return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter, - isUsedByOtherApps); + return dexOptSecondaryDexPathLI(info, path, dexUseInfo, options); } finally { releaseWakeLockLI(acquireTime); } @@ -304,13 +337,19 @@ public class PackageDexOptimizer { } @GuardedBy("mInstallLock") - private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas, - String compilerFilter, boolean isUsedByOtherApps) { - compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps); + private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, + PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) { + if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) { + // We are asked to optimize only the dex files used by other apps and this is not + // on of them: skip it. + return DEX_OPT_SKIPPED; + } + + String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(), + dexUseInfo.isUsedByOtherApps()); // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. // Secondary dex files are currently not compiled at boot. - int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true) - | DEXOPT_SECONDARY_DEX; + int dexoptFlags = getDexFlags(info, compilerFilter, options) | DEXOPT_SECONDARY_DEX; // Check the app storage and add the appropriate flags. if (info.deviceProtectedDataDir != null && FileUtils.contains(info.deviceProtectedDataDir, path)) { @@ -323,19 +362,27 @@ public class PackageDexOptimizer { return DEX_OPT_FAILED; } Log.d(TAG, "Running dexopt on: " + path - + " pkg=" + info.packageName + " isa=" + isas + + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas() + " dexoptFlags=" + printDexoptFlags(dexoptFlags) + " target-filter=" + compilerFilter); + // TODO(calin): b/64530081 b/66984396. Use SKIP_SHARED_LIBRARY_CHECK for the context + // (instead of dexUseInfo.getClassLoaderContext()) in order to compile secondary dex files + // in isolation (and avoid to extract/verify the main apk if it's in the class path). + // Note this trades correctness for performance since the resulting slow down is + // unacceptable in some cases until b/64530081 is fixed. + String classLoaderContext = SKIP_SHARED_LIBRARY_CHECK; + try { - for (String isa : isas) { + for (String isa : dexUseInfo.getLoaderIsas()) { // Reuse the same dexopt path as for the primary apks. We don't need all the // arguments as some (dexopNeeded and oatDir) will be computed by installd because // system server cannot read untrusted app content. // TODO(calin): maybe add a separate call. mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0, /*oatDir*/ null, dexoptFlags, - compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser); + compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser, + options.isDowngrade()); } return DEX_OPT_PERFORMED; @@ -363,26 +410,51 @@ public class PackageDexOptimizer { /** * Dumps the dexopt state of the given package {@code pkg} to the given {@code PrintWriter}. */ - void dumpDexoptState(IndentingPrintWriter pw, PackageParser.Package pkg) { + void dumpDexoptState(IndentingPrintWriter pw, PackageParser.Package pkg, + PackageDexUsage.PackageUseInfo useInfo) { final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); - for (String instructionSet : dexCodeInstructionSets) { - pw.println("Instruction Set: " + instructionSet); - pw.increaseIndent(); - for (String path : paths) { - String status = null; - try { - status = DexFile.getDexFileStatus(path, instructionSet); - } catch (IOException ioe) { - status = "[Exception]: " + ioe.getMessage(); - } - pw.println("path: " + path); - pw.println("status: " + status); - } - pw.decreaseIndent(); + for (String path : paths) { + pw.println("path: " + path); + pw.increaseIndent(); + + for (String isa : dexCodeInstructionSets) { + String status = null; + try { + status = DexFile.getDexFileStatus(path, isa); + } catch (IOException ioe) { + status = "[Exception]: " + ioe.getMessage(); + } + pw.println(isa + ": " + status); + } + + if (useInfo.isUsedByOtherApps(path)) { + pw.println("used by other apps: " + useInfo.getLoadingPackages(path)); + } + + Map<String, PackageDexUsage.DexUseInfo> dexUseInfoMap = useInfo.getDexUseInfoMap(); + + if (!dexUseInfoMap.isEmpty()) { + pw.println("known secondary dex files:"); + pw.increaseIndent(); + for (Map.Entry<String, PackageDexUsage.DexUseInfo> e : dexUseInfoMap.entrySet()) { + String dex = e.getKey(); + PackageDexUsage.DexUseInfo dexUseInfo = e.getValue(); + pw.println(dex); + pw.increaseIndent(); + // TODO(calin): get the status of the oat file (needs installd call) + pw.println("class loader context: " + dexUseInfo.getClassLoaderContext()); + if (dexUseInfo.isUsedByOtherApps()) { + pw.println("used by other apps: " + dexUseInfo.getLoadingPackages()); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); + } + pw.decreaseIndent(); } } @@ -400,8 +472,9 @@ public class PackageDexOptimizer { } if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) { - // If the dex files is used by other apps, we cannot use profile-guided compilation. - return getNonProfileGuidedCompilerFilter(targetCompilerFilter); + // If the dex files is used by other apps, apply the shared filter. + return PackageManagerServiceCompilerMapping.getCompilerFilterForReason( + PackageManagerService.REASON_SHARED); } return targetCompilerFilter; @@ -412,11 +485,11 @@ public class PackageDexOptimizer { * filter. */ private int getDexFlags(PackageParser.Package pkg, String compilerFilter, - boolean bootComplete) { - return getDexFlags(pkg.applicationInfo, compilerFilter, bootComplete); + DexoptOptions options) { + return getDexFlags(pkg.applicationInfo, compilerFilter, options); } - private int getDexFlags(ApplicationInfo info, String compilerFilter, boolean bootComplete) { + private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) { int flags = info.flags; boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; // Profile guide compiled oat files should not be public. @@ -427,7 +500,8 @@ public class PackageDexOptimizer { (isPublic ? DEXOPT_PUBLIC : 0) | (debuggable ? DEXOPT_DEBUGGABLE : 0) | profileFlag - | (bootComplete ? DEXOPT_BOOTCOMPLETE : 0); + | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0) + | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0); return adjustDexoptFlags(dexFlags); } @@ -436,11 +510,11 @@ public class PackageDexOptimizer { * configuration (isa, compiler filter, profile). */ private int getDexoptNeeded(String path, String isa, String compilerFilter, - boolean newProfile) { + String classLoaderContext, boolean newProfile, boolean downgrade) { int dexoptNeeded; try { - dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile, - false /* downgrade */); + dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, classLoaderContext, + newProfile, downgrade); } catch (IOException ioe) { Slog.w(TAG, "IOException reading apk: " + path, ioe); return DEX_OPT_FAILED; @@ -449,86 +523,6 @@ public class PackageDexOptimizer { } /** - * Computes the shared libraries path that should be passed to dexopt. - */ - private String getSharedLibrariesPath(String[] sharedLibraries) { - if (sharedLibraries == null || sharedLibraries.length == 0) { - return null; - } - StringBuilder sb = new StringBuilder(); - for (String lib : sharedLibraries) { - if (sb.length() != 0) { - sb.append(":"); - } - sb.append(lib); - } - return sb.toString(); - } - - /** - * Walks dependency tree and gathers the dependencies for each split in a split apk. - * The split paths are stored as relative paths, separated by colons. - */ - private String[] getSplitDependencies(PackageParser.Package pkg) { - // Convert all the code paths to relative paths. - String baseCodePath = new File(pkg.baseCodePath).getParent(); - List<String> paths = pkg.getAllCodePaths(); - String[] splitDependencies = new String[paths.size()]; - for (int i = 0; i < paths.size(); i++) { - File pathFile = new File(paths.get(i)); - String fileName = pathFile.getName(); - paths.set(i, fileName); - - // Sanity check that the base paths of the splits are all the same. - String basePath = pathFile.getParent(); - if (!basePath.equals(baseCodePath)) { - Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " + - baseCodePath); - } - } - - // If there are no other dependencies, fill in the implicit dependency on the base apk. - SparseArray<int[]> dependencies = pkg.applicationInfo.splitDependencies; - if (dependencies == null) { - for (int i = 1; i < paths.size(); i++) { - splitDependencies[i] = paths.get(0); - } - return splitDependencies; - } - - // Fill in the dependencies, skipping the base apk which has no dependencies. - for (int i = 1; i < dependencies.size(); i++) { - getParentDependencies(dependencies.keyAt(i), paths, dependencies, splitDependencies); - } - - return splitDependencies; - } - - /** - * Recursive method to generate dependencies for a particular split. - * The index is a key from the package's splitDependencies. - */ - private String getParentDependencies(int index, List<String> paths, - SparseArray<int[]> dependencies, String[] splitDependencies) { - // The base apk is always first, and has no dependencies. - if (index == 0) { - return null; - } - // Return the result if we've computed the dependencies for this index already. - if (splitDependencies[index] != null) { - return splitDependencies[index]; - } - // Get the dependencies for the parent of this index and append its path to it. - int parent = dependencies.get(index)[0]; - String parentDependencies = - getParentDependencies(parent, paths, dependencies, splitDependencies); - String path = parentDependencies == null ? paths.get(parent) : - parentDependencies + ":" + paths.get(parent); - splitDependencies[index] = path; - return path; - } - - /** * Checks if there is an update on the profile information of the {@code pkg}. * If the compiler filter is not profile guided the method returns false. * @@ -619,6 +613,9 @@ public class PackageDexOptimizer { if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) { flagsList.add("storage_de"); } + if ((flags & DEXOPT_IDLE_BACKGROUND_JOB) == DEXOPT_IDLE_BACKGROUND_JOB) { + flagsList.add("idle_background_job"); + } return String.join(",", flagsList); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index da1df78b4f47..242a76dbb63f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -280,6 +280,8 @@ import com.android.server.pm.PermissionsState.PermissionState; import com.android.server.pm.Settings.DatabaseVersion; import com.android.server.pm.Settings.VersionInfo; import com.android.server.pm.dex.DexManager; +import com.android.server.pm.dex.DexoptOptions; +import com.android.server.pm.dex.PackageDexUsage; import com.android.server.storage.DeviceStorageMonitorInternal; import dalvik.system.CloseGuard; @@ -559,8 +561,10 @@ public class PackageManagerService extends IPackageManager.Stub public static final int REASON_INSTALL = 2; public static final int REASON_BACKGROUND_DEXOPT = 3; public static final int REASON_AB_OTA = 4; + public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 5; + public static final int REASON_SHARED = 6; - public static final int REASON_LAST = REASON_AB_OTA; + public static final int REASON_LAST = REASON_SHARED; /** All dangerous permission names in the same order as the events in MetricsEvent */ private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList( @@ -9392,24 +9396,42 @@ public class PackageManagerService extends IPackageManager.Stub // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a // trade-off worth doing to save boot time work. - int dexOptStatus = performDexOptTraced(pkg.packageName, - false /* checkProfiles */, + int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0; + int primaryDexOptStaus = performDexOptTraced(new DexoptOptions( + pkg.packageName, compilerFilter, - false /* force */, - bootComplete); - switch (dexOptStatus) { - case PackageDexOptimizer.DEX_OPT_PERFORMED: - numberOfPackagesOptimized++; - break; - case PackageDexOptimizer.DEX_OPT_SKIPPED: - numberOfPackagesSkipped++; - break; - case PackageDexOptimizer.DEX_OPT_FAILED: - numberOfPackagesFailed++; - break; - default: - Log.e(TAG, "Unexpected dexopt return code " + dexOptStatus); - break; + dexoptFlags)); + + boolean secondaryDexOptStatus = true; + if (pkg.isSystemApp()) { + // Only dexopt shared secondary dex files belonging to system apps to not slow down + // too much boot after an OTA. + int secondaryDexoptFlags = dexoptFlags | + DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_ONLY_SHARED_DEX; + mDexManager.dexoptSecondaryDex(new DexoptOptions( + pkg.packageName, + compilerFilter, + secondaryDexoptFlags)); + } + + if (secondaryDexOptStatus) { + switch (primaryDexOptStaus) { + case PackageDexOptimizer.DEX_OPT_PERFORMED: + numberOfPackagesOptimized++; + break; + case PackageDexOptimizer.DEX_OPT_SKIPPED: + numberOfPackagesSkipped++; + break; + case PackageDexOptimizer.DEX_OPT_FAILED: + numberOfPackagesFailed++; + break; + default: + Log.e(TAG, "Unexpected dexopt return code " + primaryDexOptStaus); + break; + } + } else { + numberOfPackagesFailed++; } } @@ -9440,7 +9462,8 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) { + public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames, + List<String> classPaths, String loaderIsa) { int userId = UserHandle.getCallingUserId(); ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); if (ai == null) { @@ -9448,7 +9471,7 @@ public class PackageManagerService extends IPackageManager.Stub + loadingPackageName + ", user=" + userId); return; } - mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId); + mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId); } @Override @@ -9476,17 +9499,53 @@ public class PackageManagerService extends IPackageManager.Stub } } + /** + * Ask the package manager to perform a dex-opt with the given compiler filter. + * + * Note: exposed only for the shell command to allow moving packages explicitly to a + * definite state. + */ + @Override + public boolean performDexOptMode(String packageName, + boolean checkProfiles, String targetCompilerFilter, boolean force, + boolean bootComplete, String splitName) { + int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0) | + (force ? DexoptOptions.DEXOPT_FORCE : 0) | + (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0); + return performDexOpt(new DexoptOptions(packageName, targetCompilerFilter, + splitName, flags)); + } + + /** + * Ask the package manager to perform a dex-opt with the given compiler filter on the + * secondary dex files belonging to the given package. + * + * Note: exposed only for the shell command to allow moving packages explicitly to a + * definite state. + */ @Override - public boolean performDexOpt(String packageName, - boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) { + public boolean performDexOptSecondary(String packageName, String compilerFilter, + boolean force) { + int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | + DexoptOptions.DEXOPT_BOOT_COMPLETE | + (force ? DexoptOptions.DEXOPT_FORCE : 0); + return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags)); + } + + /*package*/ boolean performDexOpt(DexoptOptions options) { if (getInstantAppPackageName(Binder.getCallingUid()) != null) { return false; - } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) { + } else if (isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) { return false; } - int dexoptStatus = performDexOptWithStatus( - packageName, checkProfiles, compileReason, force, bootComplete); - return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED; + + if (options.isDexoptOnlySecondaryDex()) { + return mDexManager.dexoptSecondaryDex(options); + } else { + int dexoptStatus = performDexOptWithStatus(options); + return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED; + } } /** @@ -9495,33 +9554,14 @@ public class PackageManagerService extends IPackageManager.Stub * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} * {@link PackageDexOptimizer#DEX_OPT_FAILED} */ - /* package */ int performDexOptWithStatus(String packageName, - boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) { - return performDexOptTraced(packageName, checkProfiles, - getCompilerFilterForReason(compileReason), force, bootComplete); + /* package */ int performDexOptWithStatus(DexoptOptions options) { + return performDexOptTraced(options); } - @Override - public boolean performDexOptMode(String packageName, - boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return false; - } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) { - return false; - } - int dexOptStatus = performDexOptTraced(packageName, checkProfiles, - targetCompilerFilter, force, bootComplete); - return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED; - } - - private int performDexOptTraced(String packageName, - boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete) { + private int performDexOptTraced(DexoptOptions options) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); try { - return performDexOptInternal(packageName, checkProfiles, - targetCompilerFilter, force, bootComplete); + return performDexOptInternal(options); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -9529,12 +9569,10 @@ public class PackageManagerService extends IPackageManager.Stub // Run dexopt on a given package. Returns true if dexopt did not fail, i.e. // if the package can now be considered up to date for the given filter. - private int performDexOptInternal(String packageName, - boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete) { + private int performDexOptInternal(DexoptOptions options) { PackageParser.Package p; synchronized (mPackages) { - p = mPackages.get(packageName); + p = mPackages.get(options.getPackageName()); if (p == null) { // Package could not be found. Report failure. return PackageDexOptimizer.DEX_OPT_FAILED; @@ -9545,8 +9583,7 @@ public class PackageManagerService extends IPackageManager.Stub long callingId = Binder.clearCallingIdentity(); try { synchronized (mInstallLock) { - return performDexOptInternalWithDependenciesLI(p, checkProfiles, - targetCompilerFilter, force, bootComplete); + return performDexOptInternalWithDependenciesLI(p, options); } } finally { Binder.restoreCallingIdentity(callingId); @@ -9566,12 +9603,11 @@ public class PackageManagerService extends IPackageManager.Stub } private int performDexOptInternalWithDependenciesLI(PackageParser.Package p, - boolean checkProfiles, String targetCompilerFilter, - boolean force, boolean bootComplete) { + DexoptOptions options) { // Select the dex optimizer based on the force parameter. // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to // allocate an object here. - PackageDexOptimizer pdo = force + PackageDexOptimizer pdo = options.isForce() ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) : mPackageDexOptimizer; @@ -9585,39 +9621,19 @@ public class PackageManagerService extends IPackageManager.Stub Collection<PackageParser.Package> deps = findSharedNonSystemLibraries(p); final String[] instructionSets = getAppDexInstructionSets(p.applicationInfo); if (!deps.isEmpty()) { + DexoptOptions libraryOptions = new DexoptOptions(options.getPackageName(), + options.getCompilerFilter(), options.getSplitName(), + options.getFlags() | DexoptOptions.DEXOPT_AS_SHARED_LIBRARY); for (PackageParser.Package depPackage : deps) { // TODO: Analyze and investigate if we (should) profile libraries. pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets, - false /* checkProfiles */, - targetCompilerFilter, getOrCreateCompilerPackageStats(depPackage), - true /* isUsedByOtherApps */, - bootComplete); + mDexManager.getPackageUseInfoOrDefault(depPackage.packageName), libraryOptions); } } - return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles, - targetCompilerFilter, getOrCreateCompilerPackageStats(p), - mDexManager.isUsedByOtherApps(p.packageName), bootComplete); - } - - // Performs dexopt on the used secondary dex files belonging to the given package. - // Returns true if all dex files were process successfully (which could mean either dexopt or - // skip). Returns false if any of the files caused errors. - @Override - public boolean performDexOptSecondary(String packageName, String compilerFilter, - boolean force) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return false; - } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) { - return false; - } - mDexManager.reconcileSecondaryDexFiles(packageName); - return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force); - } - - public boolean performDexOptSecondary(String packageName, int compileReason, - boolean force) { - return mDexManager.dexoptSecondaryDex(packageName, compileReason, force); + return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, + getOrCreateCompilerPackageStats(p), + mDexManager.getPackageUseInfoOrDefault(p.packageName), options); } /** @@ -9743,6 +9759,7 @@ public class PackageManagerService extends IPackageManager.Stub public void shutdown() { mPackageUsage.writeNow(mPackages); mCompilerStats.writeNow(); + mDexManager.writePackageDexUsageNow(); } @Override @@ -9793,10 +9810,11 @@ public class PackageManagerService extends IPackageManager.Stub // Whoever is calling forceDexOpt wants a compiled package. // Don't use profiles since that may cause compilation to be skipped. - final int res = performDexOptInternalWithDependenciesLI(pkg, - false /* checkProfiles */, getDefaultCompilerFilter(), - true /* force */, - true /* bootComplete */); + final int res = performDexOptInternalWithDependenciesLI( + pkg, + new DexoptOptions(packageName, + getDefaultCompilerFilter(), + DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE)); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { @@ -16227,7 +16245,7 @@ public class PackageManagerService extends IPackageManager.Stub } } - private void removeDexFiles(List<String> allCodePaths, String[] instructionSets) { + void removeDexFiles(List<String> allCodePaths, String[] instructionSets) { if (!allCodePaths.isEmpty()) { if (instructionSets == null) { throw new IllegalStateException("instructionSet == null"); @@ -18246,34 +18264,6 @@ public class PackageManagerService extends IPackageManager.Stub Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage()); } } - - // dexopt can take some time to complete, so, for instant apps, we skip this - // step during installation. Instead, we'll take extra time the first time the - // instant app starts. It's preferred to do it this way to provide continuous - // progress to the user instead of mysteriously blocking somewhere in the - // middle of running an instant app. The default behaviour can be overridden - // via gservices. - if (!instantApp || Global.getInt( - mContext.getContentResolver(), Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); - // Do not run PackageDexOptimizer through the local performDexOpt - // method because `pkg` may not be in `mPackages` yet. - // - // Also, don't fail application installs if the dexopt step fails. - mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles, - null /* instructionSets */, false /* checkProfiles */, - getCompilerFilterForReason(REASON_INSTALL), - getOrCreateCompilerPackageStats(pkg), - mDexManager.isUsedByOtherApps(pkg.packageName), - true /* bootComplete */); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } - - // Notify BackgroundDexOptService that the package has been changed. - // If this is an update of a package which used to fail to compile, - // BDOS will remove it from its blacklist. - // TODO: Layering violation - BackgroundDexOptService.notifyPackageChanged(pkg.packageName); } if (!args.doRename(res.returnCode, pkg, oldCodePath)) { @@ -18281,6 +18271,50 @@ public class PackageManagerService extends IPackageManager.Stub return; } + // Verify if we need to dexopt the app. + // + // NOTE: it is *important* to call dexopt after doRename which will sync the + // package data from PackageParser.Package and its corresponding ApplicationInfo. + // + // We only need to dexopt if the package meets ALL of the following conditions: + // 1) it is not forward locked. + // 2) it is not on on an external ASEC container. + // 3) it is not an instant app or if it is then dexopt is enabled via gservices. + // + // Note that we do not dexopt instant apps by default. dexopt can take some time to + // complete, so we skip this step during installation. Instead, we'll take extra time + // the first time the instant app starts. It's preferred to do it this way to provide + // continuous progress to the useur instead of mysteriously blocking somewhere in the + // middle of running an instant app. The default behaviour can be overridden + // via gservices. + final boolean performDexopt = !forwardLocked + && !pkg.applicationInfo.isExternalAsec() + && (!instantApp || Global.getInt(mContext.getContentResolver(), + Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0); + + if (performDexopt) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + // Do not run PackageDexOptimizer through the local performDexOpt + // method because `pkg` may not be in `mPackages` yet. + // + // Also, don't fail application installs if the dexopt step fails. + DexoptOptions dexoptOptions = new DexoptOptions(pkg.packageName, + REASON_INSTALL, + DexoptOptions.DEXOPT_BOOT_COMPLETE); + mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles, + null /* instructionSets */, + getOrCreateCompilerPackageStats(pkg), + mDexManager.getPackageUseInfoOrDefault(pkg.packageName), + dexoptOptions); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } + + // Notify BackgroundDexOptService that the package has been changed. + // If this is an update of a package which used to fail to compile, + // BackgroundDexOptService will remove it from its blacklist. + // TODO: Layering violation + BackgroundDexOptService.notifyPackageChanged(pkg.packageName); + startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg); try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags, @@ -22536,7 +22570,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); for (PackageParser.Package pkg : packages) { ipw.println("[" + pkg.packageName + "]"); ipw.increaseIndent(); - mPackageDexOptimizer.dumpDexoptState(ipw, pkg); + mPackageDexOptimizer.dumpDexoptState(ipw, pkg, + mDexManager.getPackageUseInfoOrDefault(pkg.packageName)); ipw.decreaseIndent(); } } @@ -25039,6 +25074,29 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } } } + + Set<String> getUnusedPackages(long downgradeTimeThresholdMillis) { + Set<String> unusedPackages = new HashSet<>(); + long currentTimeInMillis = System.currentTimeMillis(); + synchronized (mPackages) { + for (PackageParser.Package pkg : mPackages.values()) { + PackageSetting ps = mSettings.mPackages.get(pkg.packageName); + if (ps == null) { + continue; + } + PackageDexUsage.PackageUseInfo packageUseInfo = + getDexManager().getPackageUseInfoOrDefault(pkg.packageName); + if (PackageManagerServiceUtils + .isUnusedSinceTimeInMillis(ps.firstInstallTime, currentTimeInMillis, + downgradeTimeThresholdMillis, packageUseInfo, + pkg.getLatestPackageUseTimeInMills(), + pkg.getLatestForegroundPackageUseTimeInMills())) { + unusedPackages.add(pkg.packageName); + } + } + } + return unusedPackages; + } } interface PackageSender { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java index ec248f5719b3..781216c3c43f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java @@ -26,14 +26,19 @@ import dalvik.system.DexFile; public class PackageManagerServiceCompilerMapping { // Names for compilation reasons. static final String REASON_STRINGS[] = { - "first-boot", "boot", "install", "bg-dexopt", "ab-ota" + "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive", "shared" }; + static final int REASON_SHARED_INDEX = 6; + // Static block to ensure the strings array is of the right length. static { if (PackageManagerService.REASON_LAST + 1 != REASON_STRINGS.length) { throw new IllegalStateException("REASON_STRINGS not correct"); } + if (!"shared".equals(REASON_STRINGS[REASON_SHARED_INDEX])) { + throw new IllegalStateException("REASON_STRINGS not correct because of shared index"); + } } private static String getSystemPropertyName(int reason) { @@ -47,16 +52,34 @@ public class PackageManagerServiceCompilerMapping { // Load the property for the given reason and check for validity. This will throw an // exception in case the reason or value are invalid. private static String getAndCheckValidity(int reason) { - String sysPropValue = SystemProperties.get(getSystemPropertyName(reason)); + String sysPropName = getSystemPropertyName(reason); + String sysPropValue; + // TODO: This is a temporary hack to keep marlin booting on aosp/master while we + // figure out how to deal with these system properties that currently appear on + // vendor. + if ("pm.dexopt.inactive".equals(sysPropName)) { + sysPropValue = "verify"; + } else if ("pm.dexopt.shared".equals(sysPropName)) { + sysPropValue = "speed"; + } else { + sysPropValue = SystemProperties.get(sysPropName); + } if (sysPropValue == null || sysPropValue.isEmpty() || !DexFile.isValidCompilerFilter(sysPropValue)) { throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid " + "(reason " + REASON_STRINGS[reason] + ")"); + } else if (!isFilterAllowedForReason(reason, sysPropValue)) { + throw new IllegalStateException("Value \"" + sysPropValue +"\" not allowed " + + "(reason " + REASON_STRINGS[reason] + ")"); } return sysPropValue; } + private static boolean isFilterAllowedForReason(int reason, String filter) { + return reason != REASON_SHARED_INDEX || !DexFile.isProfileGuidedCompilerFilter(filter); + } + // Check that the properties are set and valid. // Note: this is done in a separate method so this class can be statically initialized. static void checkProperties() { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index eb1e7bd922e3..1e0ce7a215be 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -16,6 +16,9 @@ package com.android.server.pm; +import com.android.server.pm.dex.DexManager; +import com.android.server.pm.dex.PackageDexUsage; + import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; import static com.android.server.pm.PackageManagerService.TAG; @@ -140,9 +143,11 @@ public class PackageManagerServiceUtils { sortTemp, packageManagerService); // Give priority to apps used by other apps. + DexManager dexManager = packageManagerService.getDexManager(); applyPackageFilter((pkg) -> - packageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), result, - remainingPkgs, sortTemp, packageManagerService); + dexManager.getPackageUseInfoOrDefault(pkg.packageName) + .isAnyCodePathUsedByOtherApps(), + result, remainingPkgs, sortTemp, packageManagerService); // Filter out packages that aren't recently used, add all remaining apps. // TODO: add a property to control this? @@ -186,6 +191,41 @@ public class PackageManagerServiceUtils { } /** + * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>. + * Package is considered active, if: + * 1) It was active in foreground. + * 2) It was active in background and also used by other apps. + * + * If it doesn't have sufficient information about the package, it return <code>false</code>. + */ + static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis, + long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo, + long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) { + + if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) { + return false; + } + + // If the app was active in foreground during the threshold period. + boolean isActiveInForeground = (currentTimeInMillis + - latestForegroundPackageUseTimeInMillis) + < thresholdTimeinMillis; + + if (isActiveInForeground) { + return false; + } + + // If the app was active in background during the threshold period and was used + // by other packages. + boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis + - latestPackageUseTimeInMillis) + < thresholdTimeinMillis) + && packageUseInfo.isAnyCodePathUsedByOtherApps(); + + return !isActiveInBackgroundAndUsedByOtherPackages; + } + + /** * Returns the canonicalized path of {@code path} as per {@code realpath(3)} * semantics. */ diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 20d7b28c55e1..10ceba418f22 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -365,6 +365,7 @@ class PackageManagerShellCommand extends ShellCommand { String compilationReason = null; String checkProfilesRaw = null; boolean secondaryDex = false; + String split = null; String opt; while ((opt = getNextOption()) != null) { @@ -395,6 +396,9 @@ class PackageManagerShellCommand extends ShellCommand { case "--secondary-dex": secondaryDex = true; break; + case "--split": + split = getNextArgRequired(); + break; default: pw.println("Error: Unknown option: " + opt); return 1; @@ -423,6 +427,16 @@ class PackageManagerShellCommand extends ShellCommand { return 1; } + if (allPackages && split != null) { + pw.println("-a cannot be specified together with --split"); + return 1; + } + + if (secondaryDex && split != null) { + pw.println("--secondary-dex cannot be specified together with --split"); + return 1; + } + String targetCompilerFilter; if (compilerFilter != null) { if (!DexFile.isValidCompilerFilter(compilerFilter)) { @@ -472,7 +486,7 @@ class PackageManagerShellCommand extends ShellCommand { targetCompilerFilter, forceCompilation) : mInterface.performDexOptMode(packageName, checkProfiles, targetCompilerFilter, forceCompilation, - true /* bootComplete */); + true /* bootComplete */, split); if (!result) { failedPackages.add(packageName); } @@ -1609,7 +1623,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(""); - pw.println(" compile [-m MODE | -r REASON] [-f] [-c]"); + pw.println(" compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]"); pw.println(" [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)"); pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\"."); pw.println(" Options:"); @@ -1635,6 +1649,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --reset: restore package to its post-install state"); pw.println(" --check-prof (true | false): look at profiles when doing dexopt?"); pw.println(" --secondary-dex: compile app secondary dex files"); + pw.println(" --split SPLIT: compile only the given split name"); pw.println(" bg-dexopt-job"); pw.println(" Execute the background optimizations immediately."); pw.println(" Note that the command only runs the background optimizer logic. It may"); diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index be50eeeefc24..62747547f320 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -36,6 +36,8 @@ import com.android.server.pm.PackageManagerServiceCompilerMapping; import java.io.File; import java.io.IOException; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.HashMap; import java.util.HashSet; @@ -81,6 +83,19 @@ public class DexManager { private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex + /** + * We do not record packages that have no secondary dex files or that are not used by other + * apps. This is an optimization to reduce the amount of data that needs to be written to + * disk (apps will not usually be shared so this trims quite a bit the number we record). + * + * To make this behaviour transparent to the callers which need use information on packages, + * DexManager will return this DEFAULT instance from + * {@link DexManager#getPackageUseInfoOrDefault}. It has no data about secondary dex files and + * is marked as not being used by other apps. This reflects the intended behaviour when we don't + * find the package in the underlying data file. + */ + private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo(); + public DexManager(IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock) { mPackageCodeLocationsCache = new HashMap<>(); @@ -97,29 +112,55 @@ public class DexManager { * return as fast as possible. * * @param loadingAppInfo the package performing the load - * @param dexPaths the list of dex files being loaded + * @param classLoadersNames the names of the class loaders present in the loading chain. The + * list encodes the class loader chain in the natural order. The first class loader has + * the second one as its parent and so on. The dex files present in the class path of the + * first class loader will be recorded in the usage file. + * @param classPaths the class paths corresponding to the class loaders names from + * {@param classLoadersNames}. The the first element corresponds to the first class loader + * and so on. A classpath is represented as a list of dex files separated by + * {@code File.pathSeparator}. + * The dex files found in the first class path will be recorded in the usage file. * @param loaderIsa the ISA of the app loading the dex files * @param loaderUserId the user id which runs the code loading the dex files */ - public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths, - String loaderIsa, int loaderUserId) { + public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames, + List<String> classPaths, String loaderIsa, int loaderUserId) { try { - notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId); + notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa, + loaderUserId); } catch (Exception e) { Slog.w(TAG, "Exception while notifying dex load for package " + loadingAppInfo.packageName, e); } } - private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths, - String loaderIsa, int loaderUserId) { + private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, + List<String> classLoaderNames, List<String> classPaths, String loaderIsa, + int loaderUserId) { + if (classLoaderNames.size() != classPaths.size()) { + Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size"); + return; + } + if (classLoaderNames.isEmpty()) { + Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty"); + return; + } if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { - Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " + + Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " + loaderIsa + "?"); return; } - for (String dexPath : dexPaths) { + // The classpath is represented as a list of dex files separated by File.pathSeparator. + String[] dexPathsToRegister = classPaths.get(0).split(File.pathSeparator); + + // Encode the class loader contexts for the dexPathsToRegister. + String[] classLoaderContexts = DexoptUtils.processContextForDexLoad( + classLoaderNames, classPaths); + + int dexPathIndex = 0; + for (String dexPath : dexPathsToRegister) { // Find the owning package name. DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); @@ -147,23 +188,25 @@ public class DexManager { // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, // or UsedBytOtherApps), record will return true and we trigger an async write // to disk to make sure we don't loose the data in case of a reboot. + + // A null classLoaderContexts means that there are unsupported class loaders in the + // chain. + String classLoaderContext = classLoaderContexts == null + ? PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT + : classLoaderContexts[dexPathIndex]; if (mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) { + dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, + loadingAppInfo.packageName, classLoaderContext)) { mPackageDexUsage.maybeWriteAsync(); } } else { - // This can happen in a few situations: - // - bogus dex loads - // - recent installs/uninstalls that we didn't detect. - // - new installed splits // If we can't find the owner of the dex we simply do not track it. The impact is // that the dex file will not be considered for offline optimizations. - // TODO(calin): add hooks for move/uninstall notifications to - // capture package moves or obsolete packages. if (DEBUG) { Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); } } + dexPathIndex++; } } @@ -269,6 +312,8 @@ public class DexManager { private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); + Map<String, Set<String>> packageToCodePaths = new HashMap<>(); + // Cache the code locations for the installed packages. This allows for // faster lookups (no locks) when finding what package owns the dex file. for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { @@ -278,54 +323,72 @@ public class DexManager { // Cache the code locations. cachePackageInfo(pi, userId); - // Cache a map from package name to the set of user ids who installed the package. + // Cache two maps: + // - from package name to the set of user ids who installed the package. + // - from package name to the set of code paths. // We will use it to sync the data and remove obsolete entries from // mPackageDexUsage. Set<Integer> users = putIfAbsent( packageToUsersMap, pi.packageName, new HashSet<>()); users.add(userId); + + Set<String> codePaths = putIfAbsent( + packageToCodePaths, pi.packageName, new HashSet<>()); + codePaths.add(pi.applicationInfo.sourceDir); + if (pi.applicationInfo.splitSourceDirs != null) { + Collections.addAll(codePaths, pi.applicationInfo.splitSourceDirs); + } } } mPackageDexUsage.read(); - mPackageDexUsage.syncData(packageToUsersMap); + mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); } /** * Get the package dex usage for the given package name. - * @return the package data or null if there is no data available for this package. + * If there is no usage info the method will return a default {@code PackageUseInfo} with + * no data about secondary dex files and marked as not being used by other apps. + * + * Note that no use info means the package was not used or it was used but not by other apps. + * Also, note that right now we might prune packages which are not used by other apps. + * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try + * to access the package use. */ - public PackageUseInfo getPackageUseInfo(String packageName) { - return mPackageDexUsage.getPackageUseInfo(packageName); + public PackageUseInfo getPackageUseInfoOrDefault(String packageName) { + PackageUseInfo useInfo = mPackageDexUsage.getPackageUseInfo(packageName); + return useInfo == null ? DEFAULT_USE_INFO : useInfo; } /** - * Perform dexopt on the package {@code packageName} secondary dex files. - * @return true if all secondary dex files were processed successfully (compiled or skipped - * because they don't need to be compiled).. + * Return whether or not the manager has usage information on the give package. + * + * Note that no use info means the package was not used or it was used but not by other apps. + * Also, note that right now we might prune packages which are not used by other apps. + * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try + * to access the package use. */ - public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force) { - return dexoptSecondaryDex(packageName, - PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason), - force); + /*package*/ boolean hasInfoOnPackage(String packageName) { + return mPackageDexUsage.getPackageUseInfo(packageName) != null; } /** - * Perform dexopt on the package {@code packageName} secondary dex files. + * Perform dexopt on with the given {@code options} on the secondary dex files. * @return true if all secondary dex files were processed successfully (compiled or skipped * because they don't need to be compiled).. */ - public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) { + public boolean dexoptSecondaryDex(DexoptOptions options) { // Select the dex optimizer based on the force parameter. // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust // the necessary dexopt flags to make sure that compilation is not skipped. This avoid // passing the force flag through the multitude of layers. // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to // allocate an object here. - PackageDexOptimizer pdo = force + PackageDexOptimizer pdo = options.isForce() ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) : mPackageDexOptimizer; - PackageUseInfo useInfo = getPackageUseInfo(packageName); + String packageName = options.getPackageName(); + PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { if (DEBUG) { Slog.d(TAG, "No secondary dex use for package:" + packageName); @@ -337,7 +400,8 @@ public class DexManager { for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { String dexPath = entry.getKey(); DexUseInfo dexUseInfo = entry.getValue(); - PackageInfo pkg = null; + + PackageInfo pkg; try { pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, dexUseInfo.getOwnerUserId()); @@ -356,7 +420,7 @@ public class DexManager { } int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, - dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps()); + dexUseInfo, options); success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); } return success; @@ -368,7 +432,7 @@ public class DexManager { * deleted, update the internal records and delete any generated oat files. */ public void reconcileSecondaryDexFiles(String packageName) { - PackageUseInfo useInfo = getPackageUseInfo(packageName); + PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName); if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { if (DEBUG) { Slog.d(TAG, "No secondary dex use for package:" + packageName); @@ -439,6 +503,8 @@ public class DexManager { } } + // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the + // compilation happening here will use a pessimistic context. public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath, boolean isUsedByOtherApps, int userId) { // Find the owning package record. @@ -457,11 +523,11 @@ public class DexManager { // We found the package. Now record the usage for all declared ISAs. boolean update = false; - Set<String> isas = new HashSet<>(); for (String isa : getAppDexInstructionSets(info)) { - isas.add(isa); boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false); + dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false, + searchResult.mOwningPackageName, + PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT); update |= newUpdate; } if (update) { @@ -471,8 +537,13 @@ public class DexManager { // Try to optimize the package according to the install reason. String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason( PackageManagerService.REASON_INSTALL); - int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas, - compilerFilter, isUsedByOtherApps); + DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName) + .getDexUseInfoMap().get(dexPath); + + DexoptOptions options = new DexoptOptions(info.packageName, compilerFilter, /*flags*/0); + + int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo, + options); // If we fail to optimize the package log an error but don't propagate the error // back to the app. The app cannot do much about it and the background job @@ -493,23 +564,6 @@ public class DexManager { } /** - * Return true if the profiling data collected for the given app indicate - * that the apps's APK has been loaded by another app. - * Note that this returns false for all apps without any collected profiling data. - */ - public boolean isUsedByOtherApps(String packageName) { - PackageUseInfo useInfo = getPackageUseInfo(packageName); - if (useInfo == null) { - // No use info, means the package was not used or it was used but not by other apps. - // Note that right now we might prune packages which are not used by other apps. - // TODO(calin): maybe we should not (prune) so we can have an accurate view when we try - // to access the package use. - return false; - } - return useInfo.isUsedByOtherApps(); - } - - /** * Retrieves the package which owns the given dexPath. */ private DexSearchResult getDexPackage( @@ -566,6 +620,13 @@ public class DexManager { return existingValue == null ? newValue : existingValue; } + /** + * Writes the in-memory package dex usage to disk right away. + */ + public void writePackageDexUsageNow() { + mPackageDexUsage.writeNow(); + } + public static class RegisterDexModuleResult { public RegisterDexModuleResult() { this(false, null); diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java new file mode 100644 index 000000000000..0966770d4897 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java @@ -0,0 +1,151 @@ +/* + * 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.pm.dex; + +import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; + +import android.annotation.Nullable; + +/** + * Options used for dexopt invocations. + */ +public final class DexoptOptions { + // When set, the profiles will be checked for updates before calling dexopt. If + // the apps profiles didn't update in a meaningful way (decided by the compiler), dexopt + // will be skipped. + // Currently this only affects the optimization of primary apks. Secondary dex files + // will always check the profiles for updates. + public static final int DEXOPT_CHECK_FOR_PROFILES_UPDATES = 1 << 0; + + // When set, dexopt will execute unconditionally (even if not needed). + public static final int DEXOPT_FORCE = 1 << 1; + + // Whether or not the invocation of dexopt is done after the boot is completed. This is used + // in order to adjust the priority of the compilation thread. + public static final int DEXOPT_BOOT_COMPLETE = 1 << 2; + + // When set, the dexopt invocation will optimize only the secondary dex files. If false, dexopt + // will only consider the primary apk. + public static final int DEXOPT_ONLY_SECONDARY_DEX = 1 << 3; + + // When set, dexopt will optimize only dex files that are used by other apps. + // Currently, this flag is ignored for primary apks. + public static final int DEXOPT_ONLY_SHARED_DEX = 1 << 4; + + // When set, dexopt will attempt to scale down the optimizations previously applied in order + // save disk space. + public static final int DEXOPT_DOWNGRADE = 1 << 5; + + // When set, dexopt will compile the dex file as a shared library even if it is not actually + // used by other apps. This is used to force the compilation or shared libraries declared + // with in the manifest with ''uses-library' before we have a chance to detect they are + // actually shared at runtime. + public static final int DEXOPT_AS_SHARED_LIBRARY = 1 << 6; + + // When set, indicates that dexopt is invoked from the background service. + public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9; + + // The name of package to optimize. + private final String mPackageName; + + // The intended target compiler filter. Note that dexopt might adjust the filter before the + // execution based on factors like: vmSafeMode and packageUsedByOtherApps. + private final String mCompilerFilter; + + // The set of flags for the dexopt options. It's a mix of the DEXOPT_* flags. + private final int mFlags; + + // When not null, dexopt will optimize only the split identified by this name. + // It only applies for primary apk and it's always null if mOnlySecondaryDex is true. + private final String mSplitName; + + public DexoptOptions(String packageName, String compilerFilter, int flags) { + this(packageName, compilerFilter, /*splitName*/ null, flags); + } + + public DexoptOptions(String packageName, int compilerReason, int flags) { + this(packageName, getCompilerFilterForReason(compilerReason), flags); + } + + public DexoptOptions(String packageName, String compilerFilter, String splitName, int flags) { + int validityMask = + DEXOPT_CHECK_FOR_PROFILES_UPDATES | + DEXOPT_FORCE | + DEXOPT_BOOT_COMPLETE | + DEXOPT_ONLY_SECONDARY_DEX | + DEXOPT_ONLY_SHARED_DEX | + DEXOPT_DOWNGRADE | + DEXOPT_AS_SHARED_LIBRARY | + DEXOPT_IDLE_BACKGROUND_JOB; + if ((flags & (~validityMask)) != 0) { + throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags)); + } + + mPackageName = packageName; + mCompilerFilter = compilerFilter; + mFlags = flags; + mSplitName = splitName; + } + + public String getPackageName() { + return mPackageName; + } + + public boolean isCheckForProfileUpdates() { + return (mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) != 0; + } + + public String getCompilerFilter() { + return mCompilerFilter; + } + + public boolean isForce() { + return (mFlags & DEXOPT_FORCE) != 0; + } + + public boolean isBootComplete() { + return (mFlags & DEXOPT_BOOT_COMPLETE) != 0; + } + + public boolean isDexoptOnlySecondaryDex() { + return (mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0; + } + + public boolean isDexoptOnlySharedDex() { + return (mFlags & DEXOPT_ONLY_SHARED_DEX) != 0; + } + + public boolean isDowngrade() { + return (mFlags & DEXOPT_DOWNGRADE) != 0; + } + + public boolean isDexoptAsSharedLibrary() { + return (mFlags & DEXOPT_AS_SHARED_LIBRARY) != 0; + } + + public boolean isDexoptIdleBackgroundJob() { + return (mFlags & DEXOPT_IDLE_BACKGROUND_JOB) != 0; + } + + public String getSplitName() { + return mSplitName; + } + + public int getFlags() { + return mFlags; + } +} diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java new file mode 100644 index 000000000000..c23b03153e58 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java @@ -0,0 +1,369 @@ +/* + * 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.pm.dex; + +import android.content.pm.ApplicationInfo; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.server.pm.PackageDexOptimizer; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public final class DexoptUtils { + private static final String TAG = "DexoptUtils"; + + private DexoptUtils() {} + + /** + * Creates the class loader context dependencies for each of the application code paths. + * The returned array contains the class loader contexts that needs to be passed to dexopt in + * order to ensure correct optimizations. "Code" paths with no actual code, as specified by + * {@param pathsWithCode}, are ignored and will have null as their context in the returned array + * (configuration splits are an example of paths without code). + * + * A class loader context describes how the class loader chain should be built by dex2oat + * in order to ensure that classes are resolved during compilation as they would be resolved + * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is + * loaded in a different context (with a different set of class loaders or a different + * classpath), the compiled code will be rejected. + * + * Note that the class loader context only includes dependencies and not the code path itself. + * The contexts are created based on the application split dependency list and + * the provided shared libraries. + * + * All the code paths encoded in the context will be relative to the base directory. This + * enables stage compilation where compiler artifacts may be moved around. + * + * The result is indexed as follows: + * - index 0 contains the context for the base apk + * - index 1 to n contain the context for the splits in the order determined by + * {@code info.getSplitCodePaths()} + * + * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk} + * and pay attention to the way the classpath is created for the non isolated mode in: + * {@link android.app.LoadedApk#makePaths( + * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}. + */ + public static String[] getClassLoaderContexts(ApplicationInfo info, + String[] sharedLibraries, boolean[] pathsWithCode) { + // The base class loader context contains only the shared library. + String sharedLibrariesClassPath = encodeClasspath(sharedLibraries); + String baseApkContextClassLoader = encodeClassLoader( + sharedLibrariesClassPath, "dalvik.system.PathClassLoader"); + + if (info.getSplitCodePaths() == null) { + // The application has no splits. + return new String[] {baseApkContextClassLoader}; + } + + // The application has splits. Compute their class loader contexts. + + // First, cache the relative paths of the splits and do some sanity checks + String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info); + + // The splits have an implicit dependency on the base apk. + // This means that we have to add the base apk file in addition to the shared libraries. + String baseApkName = new File(info.getBaseCodePath()).getName(); + String sharedLibrariesAndBaseClassPath = + encodeClasspath(sharedLibrariesClassPath, baseApkName); + + // The result is stored in classLoaderContexts. + // Index 0 is the class loaded context for the base apk. + // Index `i` is the class loader context encoding for split `i`. + String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length]; + classLoaderContexts[0] = pathsWithCode[0] ? baseApkContextClassLoader : null; + + if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) { + // If the app didn't request for the splits to be loaded in isolation or if it does not + // declare inter-split dependencies, then all the splits will be loaded in the base + // apk class loader (in the order of their definition). + String classpath = sharedLibrariesAndBaseClassPath; + for (int i = 1; i < classLoaderContexts.length; i++) { + classLoaderContexts[i] = pathsWithCode[i] + ? encodeClassLoader(classpath, "dalvik.system.PathClassLoader") : null; + // Note that the splits with no code are not removed from the classpath computation. + // i.e. split_n might get the split_n-1 in its classpath dependency even + // if split_n-1 has no code. + // The splits with no code do not matter for the runtime which ignores + // apks without code when doing the classpath checks. As such we could actually + // filter them but we don't do it in order to keep consistency with how the apps + // are loaded. + classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]); + } + } else { + // In case of inter-split dependencies, we need to walk the dependency chain of each + // split. We do this recursively and store intermediate results in classLoaderContexts. + + // First, look at the split class loaders and cache their individual contexts (i.e. + // the class loader + the name of the split). This is an optimization to avoid + // re-computing them during the recursive call. + // The cache is stored in splitClassLoaderEncodingCache. The difference between this and + // classLoaderContexts is that the later contains the full chain of class loaders for + // a given split while splitClassLoaderEncodingCache only contains a single class loader + // encoding. + String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length]; + for (int i = 0; i < splitRelativeCodePaths.length; i++) { + splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i], + "dalvik.system.PathClassLoader"); + } + String splitDependencyOnBase = encodeClassLoader( + sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader"); + SparseArray<int[]> splitDependencies = info.splitDependencies; + + // Note that not all splits have dependencies (e.g. configuration splits) + // The splits without dependencies will have classLoaderContexts[config_split_index] + // set to null after this step. + for (int i = 1; i < splitDependencies.size(); i++) { + int splitIndex = splitDependencies.keyAt(i); + if (pathsWithCode[splitIndex]) { + // Compute the class loader context only for the splits with code. + getParentDependencies(splitIndex, splitClassLoaderEncodingCache, + splitDependencies, classLoaderContexts, splitDependencyOnBase); + } + } + + // At this point classLoaderContexts contains only the parent dependencies. + // We also need to add the class loader of the current split which should + // come first in the context. + for (int i = 1; i < classLoaderContexts.length; i++) { + String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader"); + if (pathsWithCode[i]) { + // If classLoaderContexts[i] is null it means that the split does not have + // any dependency. In this case its context equals its declared class loader. + classLoaderContexts[i] = classLoaderContexts[i] == null + ? splitClassLoader + : encodeClassLoaderChain(splitClassLoader, classLoaderContexts[i]); + } else { + // This is a split without code, it has no dependency and it is not compiled. + // Its context will be null. + classLoaderContexts[i] = null; + } + } + } + + return classLoaderContexts; + } + + /** + * Recursive method to generate the class loader context dependencies for the split with the + * given index. {@param classLoaderContexts} acts as an accumulator. Upton return + * {@code classLoaderContexts[index]} will contain the split dependency. + * During computation, the method may resolve the dependencies of other splits as it traverses + * the entire parent chain. The result will also be stored in {@param classLoaderContexts}. + * + * Note that {@code index 0} denotes the base apk and it is special handled. When the + * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}. + * {@code classLoaderContexts[0]} is not modified in this method. + * + * @param index the index of the split (Note that index 0 denotes the base apk) + * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits. + * It contains only the split class loader and not the the base. The split + * with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}. + * @param splitDependencies the dependencies for all splits. Note that in this array index 0 + * is the base and splits start from index 1. + * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits + * start at index 1. + * @param splitDependencyOnBase the encoding of the implicit split dependency on base. + */ + private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache, + SparseArray<int[]> splitDependencies, String[] classLoaderContexts, + String splitDependencyOnBase) { + // If we hit the base apk return its custom dependency list which is + // sharedLibraries + base.apk + if (index == 0) { + return splitDependencyOnBase; + } + // Return the result if we've computed the splitDependencies for this index already. + if (classLoaderContexts[index] != null) { + return classLoaderContexts[index]; + } + // Get the splitDependencies for the parent of this index and append its path to it. + int parent = splitDependencies.get(index)[0]; + String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache, + splitDependencies, classLoaderContexts, splitDependencyOnBase); + + // The split context is: `parent context + parent dependencies context`. + String splitContext = (parent == 0) ? + parentDependencies : + encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies); + classLoaderContexts[index] = splitContext; + return splitContext; + } + + /** + * Encodes the shared libraries classpathElements in a format accepted by dexopt. + * NOTE: Keep this in sync with the dexopt expectations! Right now that is + * a list separated by ':'. + */ + private static String encodeClasspath(String[] classpathElements) { + if (classpathElements == null || classpathElements.length == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (String element : classpathElements) { + if (sb.length() != 0) { + sb.append(":"); + } + sb.append(element); + } + return sb.toString(); + } + + /** + * Adds an element to the encoding of an existing classpath. + * {@see PackageDexOptimizer.encodeClasspath(String[])} + */ + private static String encodeClasspath(String classpath, String newElement) { + return classpath.isEmpty() ? newElement : (classpath + ":" + newElement); + } + + /** + * Encodes a single class loader dependency starting from {@param path} and + * {@param classLoaderName}. + * When classpath is {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns + * the same. This special property is used only during OTA. + * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]" + * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader. + */ + /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) { + if (classpath.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) { + return classpath; + } + String classLoaderDexoptEncoding = classLoaderName; + if ("dalvik.system.PathClassLoader".equals(classLoaderName)) { + classLoaderDexoptEncoding = "PCL"; + } else { + Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName); + } + return classLoaderDexoptEncoding + "[" + classpath + "]"; + } + + /** + * Links to dependencies together in a format accepted by dexopt. + * For the special case when either of cl1 or cl2 equals + * {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns the same. This + * property is used only during OTA. + * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split + * dependencies {@see encodeClassLoader} separated by ';'. + */ + /*package*/ static String encodeClassLoaderChain(String cl1, String cl2) { + if (cl1.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK) || + cl2.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) { + return PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK; + } + if (cl1.isEmpty()) return cl2; + if (cl2.isEmpty()) return cl1; + return cl1 + ";" + cl2; + } + + /** + * Compute the class loader context for the dex files present in the classpath of the first + * class loader from the given list (referred in the code as the {@code loadingClassLoader}). + * Each dex files gets its own class loader context in the returned array. + * + * Example: + * If classLoadersNames = {"dalvik.system.DelegateLastClassLoader", + * "dalvik.system.PathClassLoader"} and classPaths = {"foo.dex:bar.dex", "other.dex"} + * The output will be + * {"DLC[];PCL[other.dex]", "DLC[foo.dex];PCL[other.dex]"} + * with "DLC[];PCL[other.dex]" being the context for "foo.dex" + * and "DLC[foo.dex];PCL[other.dex]" the context for "bar.dex". + * + * If any of the class loaders names is unsupported the method will return null. + * + * The argument lists must be non empty and of the same size. + * + * @param classLoadersNames the names of the class loaders present in the loading chain. The + * list encodes the class loader chain in the natural order. The first class loader has + * the second one as its parent and so on. + * @param classPaths the class paths for the elements of {@param classLoadersNames}. The + * the first element corresponds to the first class loader and so on. A classpath is + * represented as a list of dex files separated by {@code File.pathSeparator}. + * The return context will be for the dex files found in the first class path. + */ + /*package*/ static String[] processContextForDexLoad(List<String> classLoadersNames, + List<String> classPaths) { + if (classLoadersNames.size() != classPaths.size()) { + throw new IllegalArgumentException( + "The size of the class loader names and the dex paths do not match."); + } + if (classLoadersNames.isEmpty()) { + throw new IllegalArgumentException("Empty classLoadersNames"); + } + + // Compute the context for the parent class loaders. + String parentContext = ""; + // We know that these lists are actually ArrayLists so getting the elements by index + // is fine (they come over binder). Even if something changes we expect the sizes to be + // very small and it shouldn't matter much. + for (int i = 1; i < classLoadersNames.size(); i++) { + if (!isValidClassLoaderName(classLoadersNames.get(i))) { + return null; + } + String classpath = encodeClasspath(classPaths.get(i).split(File.pathSeparator)); + parentContext = encodeClassLoaderChain(parentContext, + encodeClassLoader(classpath, classLoadersNames.get(i))); + } + + // Now compute the class loader context for each dex file from the first classpath. + String loadingClassLoader = classLoadersNames.get(0); + if (!isValidClassLoaderName(loadingClassLoader)) { + return null; + } + String[] loadedDexPaths = classPaths.get(0).split(File.pathSeparator); + String[] loadedDexPathsContext = new String[loadedDexPaths.length]; + String currentLoadedDexPathClasspath = ""; + for (int i = 0; i < loadedDexPaths.length; i++) { + String dexPath = loadedDexPaths[i]; + String currentContext = encodeClassLoader( + currentLoadedDexPathClasspath, loadingClassLoader); + loadedDexPathsContext[i] = encodeClassLoaderChain(currentContext, parentContext); + currentLoadedDexPathClasspath = encodeClasspath(currentLoadedDexPathClasspath, dexPath); + } + return loadedDexPathsContext; + } + + // AOSP-only hack. + private static boolean isValidClassLoaderName(String name) { + return "dalvik.system.PathClassLoader".equals(name) || "dalvik.system.DexClassLoader".equals(name); + } + + /** + * Returns the relative paths of the splits declared by the application {@code info}. + * Assumes that the application declares a non-null array of splits. + */ + private static String[] getSplitRelativeCodePaths(ApplicationInfo info) { + String baseCodePath = new File(info.getBaseCodePath()).getParent(); + String[] splitCodePaths = info.getSplitCodePaths(); + String[] splitRelativeCodePaths = new String[splitCodePaths.length]; + for (int i = 0; i < splitCodePaths.length; i++) { + File pathFile = new File(splitCodePaths[i]); + splitRelativeCodePaths[i] = pathFile.getName(); + // Sanity check that the base paths of the splits are all the same. + String basePath = pathFile.getParent(); + if (!basePath.equals(baseCodePath)) { + Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " + + baseCodePath); + } + } + return splitRelativeCodePaths; + } +} diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 8a66f12cb6d9..a4a0a54b39d1 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -26,7 +26,6 @@ import com.android.server.pm.AbstractStatsBase; import com.android.server.pm.PackageManagerServiceUtils; import java.io.BufferedReader; -import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStreamReader; @@ -35,14 +34,19 @@ import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import dalvik.system.VMRuntime; import libcore.io.IoUtils; +import libcore.util.Objects; /** * Stat file which store usage information about dex files. @@ -50,19 +54,44 @@ import libcore.io.IoUtils; public class PackageDexUsage extends AbstractStatsBase<Void> { private final static String TAG = "PackageDexUsage"; - private final static int PACKAGE_DEX_USAGE_VERSION = 1; + // We support previous version to ensure that the usage list remains valid cross OTAs. + private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 = 1; + // Version 2 added: + // - the list of packages that load the dex files + // - class loader contexts for secondary dex files + // - usage for all code paths (including splits) + private final static int PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2 = 2; + + private final static int PACKAGE_DEX_USAGE_VERSION = PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2; + private final static String PACKAGE_DEX_USAGE_VERSION_HEADER = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__"; private final static String SPLIT_CHAR = ","; + private final static String CODE_PATH_LINE_CHAR = "+"; private final static String DEX_LINE_CHAR = "#"; + private final static String LOADING_PACKAGE_CHAR = "@"; + + // One of the things we record about dex files is the class loader context that was used to + // load them. That should be stable but if it changes we don't keep track of variable contexts. + // Instead we put a special marker in the dex usage file in order to recognize the case and + // skip optimizations on that dex files. + /*package*/ static final String VARIABLE_CLASS_LOADER_CONTEXT = + "=VariableClassLoaderContext="; + // The marker used for unsupported class loader contexts. + /*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT = + "=UnsupportedClassLoaderContext="; + // The markers used for unknown class loader contexts. This can happen if the dex file was + // recorded in a previous version and we didn't have a chance to update its usage. + /*package*/ static final String UNKNOWN_CLASS_LOADER_CONTEXT = + "=UnknownClassLoaderContext="; // Map which structures the information we have on a package. // Maps package name to package data (which stores info about UsedByOtherApps and // secondary dex files.). // Access to this map needs synchronized. @GuardedBy("mPackageUseInfoMap") - private Map<String, PackageUseInfo> mPackageUseInfoMap; + private final Map<String, PackageUseInfo> mPackageUseInfoMap; public PackageDexUsage() { super("package-dex-usage.list", "PackageDexUsage_DiskWriter", /*lock*/ false); @@ -75,21 +104,28 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * Note this is called when apps load dex files and as such it should return * as fast as possible. * - * @param loadingPackage the package performing the load + * @param owningPackageName the package owning the dex path * @param dexPath the path of the dex files being loaded * @param ownerUserId the user id which runs the code loading the dex files * @param loaderIsa the ISA of the app loading the dex files * @param isUsedByOtherApps whether or not this dex file was not loaded by its owning package * @param primaryOrSplit whether or not the dex file is a primary/split dex. True indicates * the file is either primary or a split. False indicates the file is secondary dex. + * @param loadingPackageName the package performing the load. Recorded only if it is different + * than {@param owningPackageName}. * @return true if the dex load constitutes new information, or false if this information * has been seen before. */ public boolean record(String owningPackageName, String dexPath, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) { + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, + String loadingPackageName, String classLoaderContext) { if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { throw new IllegalArgumentException("loaderIsa " + loaderIsa + " is unsupported"); } + if (classLoaderContext == null) { + throw new IllegalArgumentException("Null classLoaderContext"); + } + synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); if (packageUseInfo == null) { @@ -99,12 +135,16 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { // If we have a primary or a split apk, set isUsedByOtherApps. // We do not need to record the loaderIsa or the owner because we compile // primaries for all users and all ISAs. - packageUseInfo.mIsUsedByOtherApps = isUsedByOtherApps; + packageUseInfo.mergeCodePathUsedByOtherApps(dexPath, isUsedByOtherApps, + owningPackageName, loadingPackageName); } else { // For secondary dex files record the loaderISA and the owner. We'll need // to know under which user to compile and for what ISA. - packageUseInfo.mDexUseInfoMap.put( - dexPath, new DexUseInfo(isUsedByOtherApps, ownerUserId, loaderIsa)); + DexUseInfo newData = new DexUseInfo(isUsedByOtherApps, ownerUserId, + classLoaderContext, loaderIsa); + packageUseInfo.mDexUseInfoMap.put(dexPath, newData); + maybeAddLoadingPackage(owningPackageName, loadingPackageName, + newData.mLoadingPackages); } mPackageUseInfoMap.put(owningPackageName, packageUseInfo); return true; @@ -113,10 +153,14 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { if (primaryOrSplit) { // We have a possible update on the primary apk usage. Merge // isUsedByOtherApps information and return if there was an update. - return packageUseInfo.merge(isUsedByOtherApps); + return packageUseInfo.mergeCodePathUsedByOtherApps( + dexPath, isUsedByOtherApps, owningPackageName, loadingPackageName); } else { DexUseInfo newData = new DexUseInfo( - isUsedByOtherApps, ownerUserId, loaderIsa); + isUsedByOtherApps, ownerUserId, classLoaderContext, loaderIsa); + boolean updateLoadingPackages = maybeAddLoadingPackage(owningPackageName, + loadingPackageName, newData.mLoadingPackages); + DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath); if (existingData == null) { // It's the first time we see this dex file. @@ -138,7 +182,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } // Merge the information into the existing data. // Returns true if there was an update. - return existingData.merge(newData); + return existingData.merge(newData) || updateLoadingPackages; } } } @@ -157,8 +201,12 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * Convenience method for async writes which does not force the user to pass a useless * (Void) null. */ - public void maybeWriteAsync() { - maybeWriteAsync((Void) null); + /*package*/ void maybeWriteAsync() { + maybeWriteAsync(null); + } + + /*package*/ void writeNow() { + writeInternal(null); } @Override @@ -185,16 +233,18 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * * file_magic_version * package_name_1 + * +code_path1 + * @ loading_package_1_1, loading_package_1_2... + * +code_path2 + * @ loading_package_2_1, loading_package_2_2... * #dex_file_path_1_1 * user_1_1, used_by_other_app_1_1, user_isa_1_1_1, user_isa_1_1_2 + * @ loading_package_1_1_1, loading_package_1_1_2... + * class_loader_context_1_1 * #dex_file_path_1_2 * user_1_2, used_by_other_app_1_2, user_isa_1_2_1, user_isa_1_2_2 - * ... - * package_name_2 - * #dex_file_path_2_1 - * user_2_1, used_by_other_app_2_1, user_isa_2_1_1, user_isa_2_1_2 - * #dex_file_path_2_2, - * user_2_2, used_by_other_app_2_2, user_isa_2_2_1, user_isa_2_2_2 + * @ loading_package_1_2_1, loading_package_1_2_2... + * class_loader_context_1_2 * ... */ /* package */ void write(Writer out) { @@ -211,9 +261,16 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { // Write the package line. String packageName = pEntry.getKey(); PackageUseInfo packageUseInfo = pEntry.getValue(); - - fpw.println(String.join(SPLIT_CHAR, packageName, - writeBoolean(packageUseInfo.mIsUsedByOtherApps))); + fpw.println(packageName); + + // Write the code paths used by other apps. + for (Map.Entry<String, Set<String>> codeEntry : + packageUseInfo.mCodePathsUsedByOtherApps.entrySet()) { + String codePath = codeEntry.getKey(); + Set<String> loadingPackages = codeEntry.getValue(); + fpw.println(CODE_PATH_LINE_CHAR + codePath); + fpw.println(LOADING_PACKAGE_CHAR + String.join(SPLIT_CHAR, loadingPackages)); + } // Write dex file lines. for (Map.Entry<String, DexUseInfo> dEntry : packageUseInfo.mDexUseInfoMap.entrySet()) { @@ -221,11 +278,14 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { DexUseInfo dexUseInfo = dEntry.getValue(); fpw.println(DEX_LINE_CHAR + dexPath); fpw.print(String.join(SPLIT_CHAR, Integer.toString(dexUseInfo.mOwnerUserId), - writeBoolean(dexUseInfo.mIsUsedByOtherApps))); + writeBoolean(dexUseInfo.mIsUsedByOtherApps))); for (String isa : dexUseInfo.mLoaderIsas) { fpw.print(SPLIT_CHAR + isa); } fpw.println(); + fpw.println(LOADING_PACKAGE_CHAR + + String.join(SPLIT_CHAR, dexUseInfo.mLoadingPackages)); + fpw.println(dexUseInfo.getClassLoaderContext()); } } fpw.flush(); @@ -252,6 +312,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { BufferedReader in = new BufferedReader(reader); // Read header, do version check. String versionLine = in.readLine(); + int version; if (versionLine == null) { throw new IllegalStateException("No version line found."); } else { @@ -259,48 +320,56 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { // TODO(calin): the caller is responsible to clear the file. throw new IllegalStateException("Invalid version line: " + versionLine); } - int version = Integer.parseInt( + version = Integer.parseInt( versionLine.substring(PACKAGE_DEX_USAGE_VERSION_HEADER.length())); - if (version != PACKAGE_DEX_USAGE_VERSION) { + if (!isSupportedVersion(version)) { throw new IllegalStateException("Unexpected version: " + version); } } - String s = null; - String currentPakage = null; - PackageUseInfo currentPakageData = null; + String line; + String currentPackage = null; + PackageUseInfo currentPackageData = null; Set<String> supportedIsas = new HashSet<>(); for (String abi : Build.SUPPORTED_ABIS) { supportedIsas.add(VMRuntime.getInstructionSet(abi)); } - while ((s = in.readLine()) != null) { - if (s.startsWith(DEX_LINE_CHAR)) { + while ((line = in.readLine()) != null) { + if (line.startsWith(DEX_LINE_CHAR)) { // This is the start of the the dex lines. - // We expect two lines for each dex entry: + // We expect 4 lines for each dex entry: // #dexPaths + // @loading_package_1,loading_package_2,... + // class_loader_context // onwerUserId,isUsedByOtherApps,isa1,isa2 - if (currentPakage == null) { + if (currentPackage == null) { throw new IllegalStateException( "Malformed PackageDexUsage file. Expected package line before dex line."); } - // First line is the dex path. - String dexPath = s.substring(DEX_LINE_CHAR.length()); - // Next line is the dex data. - s = in.readLine(); - if (s == null) { - throw new IllegalStateException("Could not fine dexUseInfo for line: " + s); - } + // Line 1 is the dex path. + String dexPath = line.substring(DEX_LINE_CHAR.length()); - // We expect at least 3 elements (isUsedByOtherApps, userId, isa). - String[] elems = s.split(SPLIT_CHAR); + // Line 2 is the dex data: (userId, isUsedByOtherApps, isa). + line = in.readLine(); + if (line == null) { + throw new IllegalStateException("Could not find dexUseInfo line"); + } + String[] elems = line.split(SPLIT_CHAR); if (elems.length < 3) { - throw new IllegalStateException("Invalid PackageDexUsage line: " + s); + throw new IllegalStateException("Invalid PackageDexUsage line: " + line); } + + // In version 2 we added the loading packages and class loader context. + Set<String> loadingPackages = maybeReadLoadingPackages(in, version); + String classLoaderContext = maybeReadClassLoaderContext(in, version); + int ownerUserId = Integer.parseInt(elems[0]); boolean isUsedByOtherApps = readBoolean(elems[1]); - DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId); + DexUseInfo dexUseInfo = new DexUseInfo(isUsedByOtherApps, ownerUserId, + classLoaderContext, /*isa*/ null); + dexUseInfo.mLoadingPackages.addAll(loadingPackages); for (int i = 2; i < elems.length; i++) { String isa = elems[i]; if (supportedIsas.contains(isa)) { @@ -317,18 +386,37 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { "unsupported isas. dexPath=" + dexPath); continue; } - currentPakageData.mDexUseInfoMap.put(dexPath, dexUseInfo); + currentPackageData.mDexUseInfoMap.put(dexPath, dexUseInfo); + } else if (line.startsWith(CODE_PATH_LINE_CHAR)) { + // This is a code path used by other apps line. + if (version < PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { + throw new IllegalArgumentException("Unexpected code path line when parsing " + + "PackageDexUseData: " + line); + } + + // Expects 2 lines: + // +code_paths + // @loading_packages + String codePath = line.substring(CODE_PATH_LINE_CHAR.length()); + Set<String> loadingPackages = maybeReadLoadingPackages(in, version); + currentPackageData.mCodePathsUsedByOtherApps.put(codePath, loadingPackages); } else { // This is a package line. - // We expect it to be: `packageName,isUsedByOtherApps`. - String[] elems = s.split(SPLIT_CHAR); - if (elems.length != 2) { - throw new IllegalStateException("Invalid PackageDexUsage line: " + s); + if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { + currentPackage = line; + currentPackageData = new PackageUseInfo(); + } else { + // Old version (<2) + // We expect it to be: `packageName,isUsedByOtherApps`. + String[] elems = line.split(SPLIT_CHAR); + if (elems.length != 2) { + throw new IllegalStateException("Invalid PackageDexUsage line: " + line); + } + currentPackage = elems[0]; + currentPackageData = new PackageUseInfo(); + currentPackageData.mUsedByOtherAppsBeforeUpgrade = readBoolean(elems[1]); } - currentPakage = elems[0]; - currentPakageData = new PackageUseInfo(); - currentPakageData.mIsUsedByOtherApps = readBoolean(elems[1]); - data.put(currentPakage, currentPakageData); + data.put(currentPackage, currentPackageData); } } @@ -339,9 +427,67 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } /** + * Reads the class loader context encoding from the buffer {@code in} if + * {@code version} is at least {PACKAGE_DEX_USAGE_VERSION}. + */ + private String maybeReadClassLoaderContext(BufferedReader in, int version) throws IOException { + String context = null; + if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { + context = in.readLine(); + if (context == null) { + throw new IllegalStateException("Could not find the classLoaderContext line."); + } + } + // The context might be empty if we didn't have the chance to update it after a version + // upgrade. In this case return the special marker so that we recognize this is an unknown + // context. + return context == null ? UNKNOWN_CLASS_LOADER_CONTEXT : context; + } + + /** + * Reads the list of loading packages from the buffer {@code in} if + * {@code version} is at least {PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2}. + */ + private Set<String> maybeReadLoadingPackages(BufferedReader in, int version) + throws IOException { + if (version >= PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2) { + String line = in.readLine(); + if (line == null) { + throw new IllegalStateException("Could not find the loadingPackages line."); + } + // We expect that most of the times the list of loading packages will be empty. + if (line.length() == LOADING_PACKAGE_CHAR.length()) { + return Collections.emptySet(); + } else { + Set<String> result = new HashSet<>(); + Collections.addAll(result, + line.substring(LOADING_PACKAGE_CHAR.length()).split(SPLIT_CHAR)); + return result; + } + } else { + return Collections.emptySet(); + } + } + + /** + * Utility method which adds {@param loadingPackage} to {@param loadingPackages} only if it's + * not equal to {@param owningPackage} + */ + private boolean maybeAddLoadingPackage(String owningPackage, String loadingPackage, + Set<String> loadingPackages) { + return !owningPackage.equals(loadingPackage) && loadingPackages.add(loadingPackage); + } + + private boolean isSupportedVersion(int version) { + return version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_1 + || version == PACKAGE_DEX_USAGE_SUPPORTED_VERSION_2; + } + + /** * Syncs the existing data with the set of available packages by removing obsolete entries. */ - public void syncData(Map<String, Set<Integer>> packageToUsersMap) { + /*package*/ void syncData(Map<String, Set<Integer>> packageToUsersMap, + Map<String, Set<String>> packageToCodePaths) { synchronized (mPackageUseInfoMap) { Iterator<Map.Entry<String, PackageUseInfo>> pIt = mPackageUseInfoMap.entrySet().iterator(); @@ -365,8 +511,26 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { dIt.remove(); } } - if (!packageUseInfo.mIsUsedByOtherApps - && packageUseInfo.mDexUseInfoMap.isEmpty()) { + + // Sync the code paths. + Set<String> codePaths = packageToCodePaths.get(packageName); + Iterator<Map.Entry<String, Set<String>>> codeIt = + packageUseInfo.mCodePathsUsedByOtherApps.entrySet().iterator(); + while (codeIt.hasNext()) { + if (!codePaths.contains(codeIt.next().getKey())) { + codeIt.remove(); + } + } + + // In case the package was marked as used by other apps in a previous version + // propagate the flag to all the code paths. + // See mUsedByOtherAppsBeforeUpgrade docs on why it is important to do it. + if (packageUseInfo.mUsedByOtherAppsBeforeUpgrade) { + for (String codePath : codePaths) { + packageUseInfo.mergeCodePathUsedByOtherApps(codePath, true, null, null); + } + } else if (!packageUseInfo.isAnyCodePathUsedByOtherApps() + && packageUseInfo.mDexUseInfoMap.isEmpty()) { // The package is not used by other apps and we removed all its dex files // records. Remove the entire package record as well. pIt.remove(); @@ -380,14 +544,13 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * Clears the {@code usesByOtherApps} marker for the package {@code packageName}. * @return true if the package usage info was updated. */ - public boolean clearUsedByOtherApps(String packageName) { + /*package*/ boolean clearUsedByOtherApps(String packageName) { synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); - if (packageUseInfo == null || !packageUseInfo.mIsUsedByOtherApps) { + if (packageUseInfo == null) { return false; } - packageUseInfo.mIsUsedByOtherApps = false; - return true; + return packageUseInfo.clearCodePathUsedByOtherApps(); } } @@ -408,7 +571,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * @return true if the record was found and actually deleted, * false if the record doesn't exist */ - public boolean removeUserPackage(String packageName, int userId) { + /*package*/ boolean removeUserPackage(String packageName, int userId) { synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); if (packageUseInfo == null) { @@ -426,7 +589,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { } // If no secondary dex info is left and the package is not used by other apps // remove the data since it is now useless. - if (packageUseInfo.mDexUseInfoMap.isEmpty() && !packageUseInfo.mIsUsedByOtherApps) { + if (packageUseInfo.mDexUseInfoMap.isEmpty() + && !packageUseInfo.isAnyCodePathUsedByOtherApps()) { mPackageUseInfoMap.remove(packageName); updated = true; } @@ -440,7 +604,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * @return true if the record was found and actually deleted, * false if the record doesn't exist */ - public boolean removeDexFile(String packageName, String dexFile, int userId) { + /*package*/ boolean removeDexFile(String packageName, String dexFile, int userId) { synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); if (packageUseInfo == null) { @@ -462,7 +626,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { return false; } - public PackageUseInfo getPackageUseInfo(String packageName) { + /*package*/ PackageUseInfo getPackageUseInfo(String packageName) { synchronized (mPackageUseInfoMap) { PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName); // The useInfo contains a map for secondary dex files which could be modified @@ -477,7 +641,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { /** * Return all packages that contain records of secondary dex files. */ - public Set<String> getAllPackagesWithSecondaryDexFiles() { + /*package*/ Set<String> getAllPackagesWithSecondaryDexFiles() { Set<String> packages = new HashSet<>(); synchronized (mPackageUseInfoMap) { for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) { @@ -515,15 +679,6 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { throw new IllegalArgumentException("Unknown bool encoding: " + bool); } - private boolean contains(int[] array, int elem) { - for (int i = 0; i < array.length; i++) { - if (elem == array[i]) { - return true; - } - } - return false; - } - public String dump() { StringWriter sw = new StringWriter(); write(sw); @@ -534,39 +689,95 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { * Stores data on how a package and its dex files are used. */ public static class PackageUseInfo { - // This flag is for the primary and split apks. It is set to true whenever one of them - // is loaded by another app. - private boolean mIsUsedByOtherApps; + // The app's code paths that are used by other apps. + // The key is the code path and the value is the set of loading packages. + private final Map<String, Set<String>> mCodePathsUsedByOtherApps; // Map dex paths to their data (isUsedByOtherApps, owner id, loader isa). private final Map<String, DexUseInfo> mDexUseInfoMap; + // Keeps track of whether or not this package was used by other apps before + // we upgraded to VERSION 4 which records the info for each code path separately. + // This is unwanted complexity but without it we risk to profile guide compile + // something that supposed to be shared. For example: + // 1) we determine that chrome is used by another app + // 2) we take an OTA which upgrades the way we keep track of usage data + // 3) chrome doesn't get used until the background job executes + // 4) as part of the backgound job we now think that chrome is not used by others + // and we speed-profile. + // 5) as a result the next time someone uses chrome it will extract from apk since + // the compiled code will be private. + private boolean mUsedByOtherAppsBeforeUpgrade; + public PackageUseInfo() { - mIsUsedByOtherApps = false; + mCodePathsUsedByOtherApps = new HashMap<>(); mDexUseInfoMap = new HashMap<>(); } // Creates a deep copy of the `other`. public PackageUseInfo(PackageUseInfo other) { - mIsUsedByOtherApps = other.mIsUsedByOtherApps; + mCodePathsUsedByOtherApps = new HashMap<>(); + for (Map.Entry<String, Set<String>> e : other.mCodePathsUsedByOtherApps.entrySet()) { + mCodePathsUsedByOtherApps.put(e.getKey(), new HashSet<>(e.getValue())); + } + mDexUseInfoMap = new HashMap<>(); for (Map.Entry<String, DexUseInfo> e : other.mDexUseInfoMap.entrySet()) { mDexUseInfoMap.put(e.getKey(), new DexUseInfo(e.getValue())); } } - private boolean merge(boolean isUsedByOtherApps) { - boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; - mIsUsedByOtherApps = mIsUsedByOtherApps || isUsedByOtherApps; - return oldIsUsedByOtherApps != this.mIsUsedByOtherApps; + private boolean mergeCodePathUsedByOtherApps(String codePath, boolean isUsedByOtherApps, + String owningPackageName, String loadingPackage) { + if (!isUsedByOtherApps) { + // Nothing to update if the the code path is not used by other apps. + return false; + } + + boolean newCodePath = false; + Set<String> loadingPackages = mCodePathsUsedByOtherApps.get(codePath); + if (loadingPackages == null) { + loadingPackages = new HashSet<>(); + mCodePathsUsedByOtherApps.put(codePath, loadingPackages); + newCodePath = true; + } + boolean newLoadingPackage = loadingPackage != null + && !loadingPackage.equals(owningPackageName) + && loadingPackages.add(loadingPackage); + return newCodePath || newLoadingPackage; } - public boolean isUsedByOtherApps() { - return mIsUsedByOtherApps; + public boolean isUsedByOtherApps(String codePath) { + return mCodePathsUsedByOtherApps.containsKey(codePath); } public Map<String, DexUseInfo> getDexUseInfoMap() { return mDexUseInfoMap; } + + public Set<String> getLoadingPackages(String codePath) { + return mCodePathsUsedByOtherApps.getOrDefault(codePath, null); + } + + public boolean isAnyCodePathUsedByOtherApps() { + return !mCodePathsUsedByOtherApps.isEmpty(); + } + + /** + * Clears the usedByOtherApps markers from all code paths. + * Returns whether or not there was an update. + */ + /*package*/ boolean clearCodePathUsedByOtherApps() { + // Update mUsedByOtherAppsBeforeUpgrade as well to be consistent with + // the new data. This is not saved to disk so we don't need to return it. + mUsedByOtherAppsBeforeUpgrade = true; + + if (mCodePathsUsedByOtherApps.isEmpty()) { + return false; + } else { + mCodePathsUsedByOtherApps.clear(); + return true; + } + } } /** @@ -575,33 +786,59 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { public static class DexUseInfo { private boolean mIsUsedByOtherApps; private final int mOwnerUserId; + // The class loader context for the dex file. This encodes the class loader chain + // (class loader type + class path) in a format compatible to dex2oat. + // See {@code DexoptUtils.processContextForDexLoad}. + private String mClassLoaderContext; + // The instructions sets of the applications loading the dex file. private final Set<String> mLoaderIsas; + // Packages who load this dex file. + private final Set<String> mLoadingPackages; - public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId) { - this(isUsedByOtherApps, ownerUserId, null); - } - - public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String loaderIsa) { + public DexUseInfo(boolean isUsedByOtherApps, int ownerUserId, String classLoaderContext, + String loaderIsa) { mIsUsedByOtherApps = isUsedByOtherApps; mOwnerUserId = ownerUserId; + mClassLoaderContext = classLoaderContext; mLoaderIsas = new HashSet<>(); if (loaderIsa != null) { mLoaderIsas.add(loaderIsa); } + mLoadingPackages = new HashSet<>(); } // Creates a deep copy of the `other`. public DexUseInfo(DexUseInfo other) { mIsUsedByOtherApps = other.mIsUsedByOtherApps; mOwnerUserId = other.mOwnerUserId; + mClassLoaderContext = other.mClassLoaderContext; mLoaderIsas = new HashSet<>(other.mLoaderIsas); + mLoadingPackages = new HashSet<>(other.mLoadingPackages); } private boolean merge(DexUseInfo dexUseInfo) { boolean oldIsUsedByOtherApps = mIsUsedByOtherApps; mIsUsedByOtherApps = mIsUsedByOtherApps || dexUseInfo.mIsUsedByOtherApps; boolean updateIsas = mLoaderIsas.addAll(dexUseInfo.mLoaderIsas); - return updateIsas || (oldIsUsedByOtherApps != mIsUsedByOtherApps); + boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); + + String oldClassLoaderContext = mClassLoaderContext; + if (UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext)) { + // Can happen if we read a previous version. + mClassLoaderContext = dexUseInfo.mClassLoaderContext; + } else if (UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(dexUseInfo.mClassLoaderContext)) { + // We detected an unsupported context. + mClassLoaderContext = UNSUPPORTED_CLASS_LOADER_CONTEXT; + } else if (!UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext) && + !Objects.equal(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { + // We detected a context change. + mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT; + } + + return updateIsas || + (oldIsUsedByOtherApps != mIsUsedByOtherApps) || + updateLoadingPackages + || !Objects.equal(oldClassLoaderContext, mClassLoaderContext); } public boolean isUsedByOtherApps() { @@ -615,5 +852,25 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { public Set<String> getLoaderIsas() { return mLoaderIsas; } + + public Set<String> getLoadingPackages() { + return mLoadingPackages; + } + + public String getClassLoaderContext() { return mClassLoaderContext; } + + public boolean isUnsupportedClassLoaderContext() { + return UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); + } + + public boolean isUnknownClassLoaderContext() { + // The class loader context may be unknown if we loaded the data from a previous version + // which didn't save the context. + return UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); + } + + public boolean isVariableClassLoaderContext() { + return VARIABLE_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); + } } } diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS new file mode 100644 index 000000000000..6c8c9b20ecdb --- /dev/null +++ b/services/core/java/com/android/server/pm/permission/OWNERS @@ -0,0 +1,7 @@ +per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com +per-file DefaultPermissionGrantPolicy.java = fkupolov@google.com +per-file DefaultPermissionGrantPolicy.java = hackbod@android.com +per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com +per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com +per-file DefaultPermissionGrantPolicy.java = toddke@google.com +per-file DefaultPermissionGrantPolicy.java = yamasani@google.com diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java index bc0f6e4b283b..6e6259d902dc 100644 --- a/services/core/java/com/android/server/timezone/IntentHelperImpl.java +++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.PatternMatcher; +import android.os.UserHandle; import android.util.Slog; /** @@ -76,7 +77,9 @@ final class IntentHelperImpl implements IntentHelper { // not expected to need local data. Receiver packageUpdateReceiver = new Receiver(packageTracker); - mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter); + mContext.registerReceiverAsUser( + packageUpdateReceiver, UserHandle.SYSTEM, packageIntentFilter, + null /* broadcastPermission */, null /* default handler */); } /** Sends an intent to trigger an update check. */ diff --git a/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java index 2e0c21bf007d..b89dd3838ae9 100644 --- a/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java +++ b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java @@ -26,6 +26,7 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.SystemClock; +import android.os.UserHandle; import android.util.Slog; import java.util.List; @@ -114,8 +115,8 @@ final class PackageTrackerHelperImpl implements ClockHelper, ConfigHelper, Packa @Override public boolean contentProviderRegistered(String authority, String requiredPackageName) { int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; - ProviderInfo providerInfo = - mPackageManager.resolveContentProvider(authority, flags); + ProviderInfo providerInfo = mPackageManager.resolveContentProviderAsUser( + authority, flags, UserHandle.SYSTEM.getIdentifier()); if (providerInfo == null) { Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority=" + authority); @@ -136,7 +137,8 @@ final class PackageTrackerHelperImpl implements ClockHelper, ConfigHelper, Packa throws PackageManager.NameNotFoundException { int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; - List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceivers(intent, flags); + List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceiversAsUser( + intent, flags, UserHandle.SYSTEM); if (resolveInfo.size() != 1) { Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for" + " intent=" + intent + ", found=" + resolveInfo); diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java index 6824a597a5f8..52b49baf1073 100644 --- a/services/core/java/com/android/server/timezone/RulesManagerService.java +++ b/services/core/java/com/android/server/timezone/RulesManagerService.java @@ -47,6 +47,7 @@ import java.util.Arrays; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import libcore.icu.ICU; +import libcore.util.TimeZoneFinder; import libcore.util.ZoneInfoDB; import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED; @@ -479,9 +480,10 @@ public final class RulesManagerService extends IRulesManager.Stub { case 'a': { // Report the active rules version (i.e. the rules in use by the current // process). - pw.println("Active rules version (ICU, libcore): " + pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): " + ICU.getTZDataVersion() + "," - + ZoneInfoDB.getInstance().getVersion()); + + ZoneInfoDB.getInstance().getVersion() + "," + + TimeZoneFinder.getInstance().getIanaVersion()); break; } default: { @@ -494,8 +496,10 @@ public final class RulesManagerService extends IRulesManager.Stub { } pw.println("RulesManagerService state: " + toString()); - pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + "," - + ZoneInfoDB.getInstance().getVersion()); + pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): " + + ICU.getTZDataVersion() + "," + + ZoneInfoDB.getInstance().getVersion() + "," + + TimeZoneFinder.getInstance().getIanaVersion()); pw.println("Distro state: " + rulesState.toString()); mPackageTracker.dump(pw); } diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java index 2359fab41260..24f1cf340968 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/services/net/java/android/net/ip/IpClient.java @@ -310,12 +310,12 @@ public class IpClient extends StateMachine { return this; } - public Builder withIPv6AddrGenModeEUI64() { + public Builder withRandomMacAddress() { mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64; return this; } - public Builder withIPv6AddrGenModeStablePrivacy() { + public Builder withStableMacAddress() { mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; return this; } diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index b12cb32cd5f1..38981453e2e1 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -88,16 +88,6 @@ public class IpManager extends IpClient { return this; } @Override - public Builder withIPv6AddrGenModeEUI64() { - super.withIPv6AddrGenModeEUI64(); - return this; - } - @Override - public Builder withIPv6AddrGenModeStablePrivacy() { - super.withIPv6AddrGenModeStablePrivacy(); - return this; - } - @Override public Builder withNetwork(Network network) { super.withNetwork(network); return this; diff --git a/services/net/java/android/net/util/VersionedBroadcastListener.java b/services/net/java/android/net/util/VersionedBroadcastListener.java index 115aa464f4f1..107c40495f9e 100644 --- a/services/net/java/android/net/util/VersionedBroadcastListener.java +++ b/services/net/java/android/net/util/VersionedBroadcastListener.java @@ -61,10 +61,6 @@ public class VersionedBroadcastListener { mGenerationNumber = new AtomicInteger(0); } - public int generationNumber() { - return mGenerationNumber.get(); - } - public void startListening() { if (DBG) Log.d(mTag, "startListening"); if (mReceiver != null) return; diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index afc0f67fe993..4db9a30a11ca 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -23,10 +23,14 @@ import android.os.UserHandle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import dalvik.system.DelegateLastClassLoader; +import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -48,6 +52,10 @@ import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; @RunWith(AndroidJUnit4.class) @SmallTest public class DexManagerTests { + private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); + private static final String DELEGATE_LAST_CLASS_LOADER_NAME = + DelegateLastClassLoader.class.getName(); + private DexManager mDexManager; private TestData mFooUser0; @@ -56,6 +64,9 @@ public class DexManagerTests { private TestData mInvalidIsa; private TestData mDoesNotExist; + private TestData mBarUser0UnsupportedClassLoader; + private TestData mBarUser0DelegateLastClassLoader; + private int mUser0; private int mUser1; @@ -68,12 +79,17 @@ public class DexManagerTests { String foo = "foo"; String bar = "bar"; - mFooUser0 = new TestData(foo, isa, mUser0); - mBarUser0 = new TestData(bar, isa, mUser0); - mBarUser1 = new TestData(bar, isa, mUser1); + mFooUser0 = new TestData(foo, isa, mUser0, PATH_CLASS_LOADER_NAME); + mBarUser0 = new TestData(bar, isa, mUser0, PATH_CLASS_LOADER_NAME); + mBarUser1 = new TestData(bar, isa, mUser1, PATH_CLASS_LOADER_NAME); mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0); mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1); + mBarUser0UnsupportedClassLoader = new TestData(bar, isa, mUser0, + "unsupported.class_loader"); + mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0, + DELEGATE_LAST_CLASS_LOADER_NAME); + mDexManager = new DexManager(null, null, null, null); // Foo and Bar are available to user0. @@ -90,7 +106,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0); // Package is not used by others, so we should get nothing back. - assertNull(getPackageUseInfo(mFooUser0)); + assertNoUseInfo(mFooUser0); } @Test @@ -100,8 +116,7 @@ public class DexManagerTests { // Bar is used by others now and should be in our records PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); } @@ -112,8 +127,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, fooSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -125,8 +139,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, barSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); } @@ -149,8 +162,7 @@ public class DexManagerTests { // Check bar usage. Should be used by other app (for primary and barSecondaries). PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertEquals(barSecondaries.size() + barSecondariesForOwnUse.size(), pui.getDexUseInfoMap().size()); @@ -160,8 +172,7 @@ public class DexManagerTests { // Check foo usage. Should not be used by other app. pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -169,22 +180,22 @@ public class DexManagerTests { @Test public void testPackageUseInfoNotFound() { // Assert we don't get back data we did not previously record. - assertNull(getPackageUseInfo(mFooUser0)); + assertNoUseInfo(mFooUser0); } @Test public void testInvalidIsa() { // Notifying with an invalid ISA should be ignored. notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0); - assertNull(getPackageUseInfo(mInvalidIsa)); + assertNoUseInfo(mInvalidIsa); } @Test - public void testNotExistingPackate() { + public void testNotExistingPackage() { // Notifying about the load of a package which was previously not // register in DexManager#load should be ignored. notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); - assertNull(getPackageUseInfo(mDoesNotExist)); + assertNoUseInfo(mDoesNotExist); } @Test @@ -192,7 +203,7 @@ public class DexManagerTests { // Bar from User1 tries to load secondary dex files from User0 Bar. // Request should be ignored. notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1); - assertNull(getPackageUseInfo(mBarUser1)); + assertNoUseInfo(mBarUser1); } @Test @@ -201,7 +212,7 @@ public class DexManagerTests { // Note that the PackageManagerService already filters this out but we // still check that nothing goes unexpected in DexManager. notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1); - assertNull(getPackageUseInfo(mBarUser1)); + assertNoUseInfo(mBarUser1); } @Test @@ -213,7 +224,7 @@ public class DexManagerTests { // Before we notify about the installation of the newPackage if mFoo // is trying to load something from it we should not find it. notifyDexLoad(mFooUser0, newSecondaries, mUser0); - assertNull(getPackageUseInfo(newPackage)); + assertNoUseInfo(newPackage); // Notify about newPackage install and let mFoo load its dexes. mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0); @@ -221,8 +232,7 @@ public class DexManagerTests { // We should get back the right info. PackageUseInfo pui = getPackageUseInfo(newPackage); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); } @@ -238,8 +248,7 @@ public class DexManagerTests { notifyDexLoad(newPackage, newSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(newPackage); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); } @@ -251,8 +260,7 @@ public class DexManagerTests { // Bar is used by others now and should be in our records. PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); // Notify that bar is updated. @@ -262,8 +270,7 @@ public class DexManagerTests { // The usedByOtherApps flag should be clear now. pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mBarUser0, pui, false); } @Test @@ -275,8 +282,7 @@ public class DexManagerTests { // We shouldn't find yet the new split as we didn't notify the package update. notifyDexLoad(mFooUser0, newSplits, mUser0); - PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNull(pui); + assertNoUseInfo(mBarUser0); // Notify that bar is updated. splitSourceDirs will contain the updated path. mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), @@ -285,9 +291,9 @@ public class DexManagerTests { // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers. notifyDexLoad(mFooUser0, newSplits, mUser0); - pui = getPackageUseInfo(mBarUser0); + PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(newSplits, pui, true); } @Test @@ -319,8 +325,7 @@ public class DexManagerTests { // Foo should still be around since it's used by other apps but with no // secondary dex info. PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); - assertTrue(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); } @@ -334,8 +339,7 @@ public class DexManagerTests { // Foo should not be around since all its secondary dex info were deleted // and it is not used by other apps. - PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNull(pui); + assertNoUseInfo(mFooUser0); } @Test @@ -347,8 +351,7 @@ public class DexManagerTests { mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), UserHandle.USER_ALL); // Bar should not be around since it was removed for all users. - PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNull(pui); + assertNoUseInfo(mBarUser0); } @Test @@ -357,7 +360,7 @@ public class DexManagerTests { // Load a dex file from framework. notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); // The dex file should not be recognized as a package. - assertNull(mDexManager.getPackageUseInfo(frameworkDex)); + assertFalse(mDexManager.hasInfoOnPackage(frameworkDex)); } @Test @@ -367,14 +370,83 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, fooSecondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertNotNull(pui); - assertFalse(pui.isUsedByOtherApps()); + assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); } + @Test + public void testNotifyUnsupportedClassLoader() { + List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths(); + notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); + + PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); + assertIsUsedByOtherApps(mBarUser0UnsupportedClassLoader, pui, false); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + // We expect that all the contexts are unsupported. + String[] expectedContexts = + Collections.nCopies(secondaries.size(), + PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT).toArray(new String[0]); + assertSecondaryUse(mBarUser0UnsupportedClassLoader, pui, secondaries, + /*isUsedByOtherApps*/false, mUser0, expectedContexts); + } + + @Test + public void testNotifyVariableClassLoader() { + // Record bar secondaries with the default PathClassLoader. + List<String> secondaries = mBarUser0.getSecondaryDexPaths(); + + notifyDexLoad(mBarUser0, secondaries, mUser0); + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertIsUsedByOtherApps(mBarUser0, pui, false); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); + + // Record bar secondaries again with a different class loader. This will change the context. + notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0); + + pui = getPackageUseInfo(mBarUser0); + assertIsUsedByOtherApps(mBarUser0, pui, false); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + // We expect that all the contexts to be changed to variable now. + String[] expectedContexts = + Collections.nCopies(secondaries.size(), + PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT).toArray(new String[0]); + assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0, + expectedContexts); + } + + @Test + public void testNotifyUnsupportedClassLoaderDoesNotChange() { + List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths(); + notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); + + PackageUseInfo pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); + assertIsUsedByOtherApps(mBarUser0UnsupportedClassLoader, pui, false); + assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); + // We expect that all the contexts are unsupported. + String[] expectedContexts = + Collections.nCopies(secondaries.size(), + PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT).toArray(new String[0]); + assertSecondaryUse(mBarUser0UnsupportedClassLoader, pui, secondaries, + /*isUsedByOtherApps*/false, mUser0, expectedContexts); + + // Record bar secondaries again with a different class loader. This will change the context. + // However, because the context was already marked as unsupported we should not chage it. + notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0); + pui = getPackageUseInfo(mBarUser0UnsupportedClassLoader); + assertSecondaryUse(mBarUser0UnsupportedClassLoader, pui, secondaries, + /*isUsedByOtherApps*/false, mUser0, expectedContexts); + + } + + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, - List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { + List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, + String[] expectedContexts) { + assertNotNull(expectedContexts); + assertEquals(expectedContexts.length, secondaries.size()); + int index = 0; for (String dex : secondaries) { DexUseInfo dui = pui.getDexUseInfoMap().get(dex); assertNotNull(dui); @@ -382,16 +454,50 @@ public class DexManagerTests { assertEquals(ownerUserId, dui.getOwnerUserId()); assertEquals(1, dui.getLoaderIsas().size()); assertTrue(dui.getLoaderIsas().contains(testData.mLoaderIsa)); + assertEquals(expectedContexts[index++], dui.getClassLoaderContext()); } } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, + List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { + String[] expectedContexts = DexoptUtils.processContextForDexLoad( + Arrays.asList(testData.mClassLoader), + Arrays.asList(String.join(File.pathSeparator, secondaries))); + assertSecondaryUse(testData, pui, secondaries, isUsedByOtherApps, ownerUserId, + expectedContexts); + } + + private void assertIsUsedByOtherApps(TestData testData, PackageUseInfo pui, + boolean isUsedByOtherApps) { + assertIsUsedByOtherApps(testData.getBaseAndSplitDexPaths(), pui, isUsedByOtherApps); + } + private void assertIsUsedByOtherApps(List<String> codePaths, PackageUseInfo pui, + boolean isUsedByOtherApps) { + for (String codePath : codePaths) { + assertEquals(codePath, isUsedByOtherApps, pui.isUsedByOtherApps(codePath)); + } + } private void notifyDexLoad(TestData testData, List<String> dexPaths, int loaderUserId) { - mDexManager.notifyDexLoad(testData.mPackageInfo.applicationInfo, dexPaths, + // By default, assume a single class loader in the chain. + // This makes writing tests much easier. + List<String> classLoaders = Arrays.asList(testData.mClassLoader); + List<String> classPaths = Arrays.asList(String.join(File.pathSeparator, dexPaths)); + notifyDexLoad(testData, classLoaders, classPaths, loaderUserId); + } + + private void notifyDexLoad(TestData testData, List<String> classLoader, List<String> classPaths, + int loaderUserId) { + mDexManager.notifyDexLoad(testData.mPackageInfo.applicationInfo, classLoader, classPaths, testData.mLoaderIsa, loaderUserId); } private PackageUseInfo getPackageUseInfo(TestData testData) { - return mDexManager.getPackageUseInfo(testData.mPackageInfo.packageName); + assertTrue(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName)); + return mDexManager.getPackageUseInfoOrDefault(testData.mPackageInfo.packageName); + } + + private void assertNoUseInfo(TestData testData) { + assertFalse(mDexManager.hasInfoOnPackage(testData.mPackageInfo.packageName)); } private static PackageInfo getMockPackageInfo(String packageName, int userId) { @@ -416,10 +522,16 @@ public class DexManagerTests { private static class TestData { private final PackageInfo mPackageInfo; private final String mLoaderIsa; + private final String mClassLoader; - private TestData(String packageName, String loaderIsa, int userId) { + private TestData(String packageName, String loaderIsa, int userId, String classLoader) { mPackageInfo = getMockPackageInfo(packageName, userId); mLoaderIsa = loaderIsa; + mClassLoader = classLoader; + } + + private TestData(String packageName, String loaderIsa, int userId) { + this(packageName, loaderIsa, userId, PATH_CLASS_LOADER_NAME); } private String getPackageName() { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java new file mode 100644 index 000000000000..c2072df75bd8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -0,0 +1,143 @@ +/* + * 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.pm.dex; + + +import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.server.pm.PackageManagerService; +import com.android.server.pm.PackageManagerServiceCompilerMapping; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DexoptOptionsTests { + private final static String mPackageName = "test.android.com"; + private final static String mCompilerFilter = + PackageManagerServiceCompilerMapping.getDefaultCompilerFilter(); + private final static String mSplitName = "split-A.apk"; + + @Test + public void testCreateDexoptOptionsEmpty() { + DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, /*flags*/ 0); + assertEquals(mPackageName, opt.getPackageName()); + assertEquals(mCompilerFilter, opt.getCompilerFilter()); + assertEquals(null, opt.getSplitName()); + assertFalse(opt.isBootComplete()); + assertFalse(opt.isCheckForProfileUpdates()); + assertFalse(opt.isDexoptOnlySecondaryDex()); + assertFalse(opt.isDexoptOnlySharedDex()); + assertFalse(opt.isDowngrade()); + assertFalse(opt.isForce()); + assertFalse(opt.isDexoptIdleBackgroundJob()); + } + + @Test + public void testCreateDexoptOptionsFull() { + int flags = + DexoptOptions.DEXOPT_FORCE | + DexoptOptions.DEXOPT_BOOT_COMPLETE | + DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | + DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_ONLY_SHARED_DEX | + DexoptOptions.DEXOPT_DOWNGRADE | + DexoptOptions.DEXOPT_AS_SHARED_LIBRARY | + DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB; + + DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags); + assertEquals(mPackageName, opt.getPackageName()); + assertEquals(mCompilerFilter, opt.getCompilerFilter()); + assertEquals(null, opt.getSplitName()); + assertTrue(opt.isBootComplete()); + assertTrue(opt.isCheckForProfileUpdates()); + assertTrue(opt.isDexoptOnlySecondaryDex()); + assertTrue(opt.isDexoptOnlySharedDex()); + assertTrue(opt.isDowngrade()); + assertTrue(opt.isForce()); + assertTrue(opt.isDexoptAsSharedLibrary()); + assertTrue(opt.isDexoptIdleBackgroundJob()); + } + + @Test + public void testCreateDexoptOptionsReason() { + int flags = + DexoptOptions.DEXOPT_FORCE | + DexoptOptions.DEXOPT_BOOT_COMPLETE | + DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES; + + int[] reasons = new int[] { + PackageManagerService.REASON_FIRST_BOOT, + PackageManagerService.REASON_BOOT, + PackageManagerService.REASON_INSTALL, + PackageManagerService.REASON_BACKGROUND_DEXOPT, + PackageManagerService.REASON_AB_OTA, + PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE,}; + + for (int reason : reasons) { + DexoptOptions opt = new DexoptOptions(mPackageName, reason, flags); + assertEquals(mPackageName, opt.getPackageName()); + assertEquals(getCompilerFilterForReason(reason), opt.getCompilerFilter()); + assertEquals(null, opt.getSplitName()); + assertTrue(opt.isBootComplete()); + assertTrue(opt.isCheckForProfileUpdates()); + assertFalse(opt.isDexoptOnlySecondaryDex()); + assertFalse(opt.isDexoptOnlySharedDex()); + assertFalse(opt.isDowngrade()); + assertTrue(opt.isForce()); + assertFalse(opt.isDexoptAsSharedLibrary()); + } + } + + @Test + public void testCreateDexoptOptionsSplit() { + int flags = DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE; + + DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, mSplitName, flags); + assertEquals(mPackageName, opt.getPackageName()); + assertEquals(mCompilerFilter, opt.getCompilerFilter()); + assertEquals(mSplitName, opt.getSplitName()); + assertTrue(opt.isBootComplete()); + assertFalse(opt.isCheckForProfileUpdates()); + assertFalse(opt.isDexoptOnlySecondaryDex()); + assertFalse(opt.isDexoptOnlySharedDex()); + assertFalse(opt.isDowngrade()); + assertTrue(opt.isForce()); + assertFalse(opt.isDexoptAsSharedLibrary()); + } + + @Test + public void testCreateDexoptInvalid() { + boolean gotException = false; + try { + int invalidFlags = 999; + new DexoptOptions(mPackageName, mCompilerFilter, invalidFlags); + } catch (IllegalArgumentException ignore) { + gotException = true; + } + + assertTrue(gotException); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java new file mode 100644 index 000000000000..34dc1adf3430 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -0,0 +1,420 @@ +/* + * 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.pm.dex; + +import com.android.server.pm.PackageDexOptimizer; + +import static com.android.server.pm.PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.pm.ApplicationInfo; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.SparseArray; + +import dalvik.system.DelegateLastClassLoader; +import dalvik.system.DexClassLoader; +import dalvik.system.PathClassLoader; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DexoptUtilsTest { + private static final String DEX_CLASS_LOADER_NAME = DexClassLoader.class.getName(); + private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); + private static final String DELEGATE_LAST_CLASS_LOADER_NAME = + DelegateLastClassLoader.class.getName(); + + private static class TestData { + ApplicationInfo info; + boolean[] pathsWithCode; + } + + private TestData createMockApplicationInfo(String baseClassLoader, boolean addSplits, + boolean addSplitDependencies) { + ApplicationInfo ai = new ApplicationInfo(); + String codeDir = "/data/app/mock.android.com"; + ai.setBaseCodePath(codeDir + "/base.dex"); + ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING; + boolean[] pathsWithCode; + if (!addSplits) { + pathsWithCode = new boolean[] {true}; + } else { + pathsWithCode = new boolean[9]; + Arrays.fill(pathsWithCode, true); + pathsWithCode[7] = false; // config split + + ai.setSplitCodePaths(new String[]{ + codeDir + "/base-1.dex", + codeDir + "/base-2.dex", + codeDir + "/base-3.dex", + codeDir + "/base-4.dex", + codeDir + "/base-5.dex", + codeDir + "/base-6.dex", + codeDir + "/config-split-7.dex", + codeDir + "/feature-no-deps.dex"}); + + String[] splitClassLoaderNames = new String[]{ + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + null, // A null class loader name should default to PathClassLoader. + null, // The config split gets a null class loader. + null}; // The feature split with no dependency and no specified class loader. + if (addSplitDependencies) { + ai.splitDependencies = new SparseArray<>(splitClassLoaderNames.length + 1); + ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency + ai.splitDependencies.put(1, new int[] {2}); // split 1 depends on 2 + ai.splitDependencies.put(2, new int[] {4}); // split 2 depends on 4 + ai.splitDependencies.put(3, new int[] {4}); // split 3 depends on 4 + ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base + ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base + ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5 + // Do not add the config split to the dependency list. + // Do not add the feature split with no dependency to the dependency list. + } + } + TestData data = new TestData(); + data.info = ai; + data.pathsWithCode = pathsWithCode; + return data; + } + + @Test + public void testSplitChain() { + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", + contexts[1]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency + } + + @Test + public void testSplitChainNoSplitDependencies() { + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]); + assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]); + assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex]", contexts[3]); + assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]); + assertEquals( + "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", + contexts[5]); + assertEquals( + "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", + contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals( + "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]", + contexts[8]); // feature split with no dependency + } + + @Test + public void testSplitChainNoIsolationNoSharedLibrary() { + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); + data.info.privateFlags = data.info.privateFlags + & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals("PCL[]", contexts[0]); + assertEquals("PCL[base.dex]", contexts[1]); + assertEquals("PCL[base.dex:base-1.dex]", contexts[2]); + assertEquals("PCL[base.dex:base-1.dex:base-2.dex]", contexts[3]); + assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]); + assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", contexts[5]); + assertEquals( + "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", + contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals( + "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]", + contexts[8]); // feature split with no dependency + } + + @Test + public void testSplitChainNoSharedLibraries() { + TestData data = createMockApplicationInfo( + DELEGATE_LAST_CLASS_LOADER_NAME, true, true); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals("PCL[]", contexts[0]); + assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[base.dex]", contexts[1]); + assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[2]); + assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[3]); + assertEquals("PCL[];PCL[base.dex]", contexts[4]); + assertEquals("PCL[];PCL[base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency + } + + @Test + public void testSplitChainWithNullPrimaryClassLoader() { + // A null classLoaderName should mean PathClassLoader. + TestData data = createMockApplicationInfo(null, true, true); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency + } + + @Test + public void tesNoSplits() { + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(1, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + } + + @Test + public void tesNoSplitsNullClassLoaderName() { + TestData data = createMockApplicationInfo(null, false, false); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(1, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + } + + @Test + public void tesNoSplitDelegateLast() { + TestData data = createMockApplicationInfo( + DELEGATE_LAST_CLASS_LOADER_NAME, false, false); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(1, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + } + + @Test + public void tesNoSplitsNoSharedLibraries() { + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); + + assertEquals(1, contexts.length); + assertEquals("PCL[]", contexts[0]); + } + + @Test + public void tesNoSplitDelegateLastNoSharedLibraries() { + TestData data = createMockApplicationInfo( + DELEGATE_LAST_CLASS_LOADER_NAME, false, false); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); + + assertEquals(1, contexts.length); + assertEquals("PCL[]", contexts[0]); + } + + @Test + public void testContextWithNoCode() { + TestData data = createMockApplicationInfo(null, true, false); + Arrays.fill(data.pathsWithCode, false); + + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals(null, contexts[0]); + assertEquals(null, contexts[1]); + assertEquals(null, contexts[2]); + assertEquals(null, contexts[3]); + assertEquals(null, contexts[4]); + assertEquals(null, contexts[5]); + assertEquals(null, contexts[6]); + assertEquals(null, contexts[7]); + } + + @Test + public void testContextBaseNoCode() { + TestData data = createMockApplicationInfo(null, true, true); + data.pathsWithCode[0] = false; + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals(null, contexts[0]); + assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); + } + + @Test + public void testProcessContextForDexLoad() { + List<String> classLoaders = Arrays.asList( + DELEGATE_LAST_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME, + PATH_CLASS_LOADER_NAME); + List<String> classPaths = Arrays.asList( + String.join(File.pathSeparator, "foo.dex", "bar.dex"), + String.join(File.pathSeparator, "parent1.dex"), + String.join(File.pathSeparator, "parent2.dex", "parent3.dex")); + String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); + assertNotNull(context); + assertEquals(2, context.length); + assertEquals("PCL[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]); + assertEquals("PCL[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]); + } + + @Test + public void testProcessContextForDexLoadSingleElement() { + List<String> classLoaders = Arrays.asList(PATH_CLASS_LOADER_NAME); + List<String> classPaths = Arrays.asList( + String.join(File.pathSeparator, "foo.dex", "bar.dex", "zoo.dex")); + String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); + assertNotNull(context); + assertEquals(3, context.length); + assertEquals("PCL[]", context[0]); + assertEquals("PCL[foo.dex]", context[1]); + assertEquals("PCL[foo.dex:bar.dex]", context[2]); + } + + @Test + public void testProcessContextForDexLoadUnsupported() { + List<String> classLoaders = Arrays.asList( + DELEGATE_LAST_CLASS_LOADER_NAME, + "unsupported.class.loader"); + List<String> classPaths = Arrays.asList( + String.join(File.pathSeparator, "foo.dex", "bar.dex"), + String.join(File.pathSeparator, "parent1.dex")); + String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); + assertNull(context); + } + + @Test + public void testProcessContextForDexLoadIllegalCallEmptyList() { + boolean gotException = false; + try { + DexoptUtils.processContextForDexLoad(Collections.emptyList(), Collections.emptyList()); + } catch (IllegalArgumentException ignore) { + gotException = true; + } + assertTrue(gotException); + } + + @Test + public void testProcessContextForDexLoadIllegalCallDifferentSize() { + boolean gotException = false; + try { + DexoptUtils.processContextForDexLoad(Collections.emptyList(), Arrays.asList("a")); + } catch (IllegalArgumentException ignore) { + gotException = true; + } + assertTrue(gotException); + } + + @Test + public void testEncodeClassLoader() { + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( + SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.PathClassLoader")); + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( + SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DexClassLoader")); + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader( + SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DelegateLastClassLoader")); + assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", + "dalvik.system.PathClassLoader")); + assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", + "dalvik.system.DexClassLoader")); + assertEquals("DLC[xyz]", DexoptUtils.encodeClassLoader("xyz", + "dalvik.system.DelegateLastClassLoader")); + assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz", null)); + assertEquals("abc[xyz]", DexoptUtils.encodeClassLoader("xyz", "abc")); + + try { + DexoptUtils.encodeClassLoader(null, "abc"); + fail(); // Exception should be caught. + } catch (NullPointerException expected) {} + } + + @Test + public void testEncodeClassLoaderChain() { + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain( + SKIP_SHARED_LIBRARY_CHECK, "PCL[a]")); + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]", + SKIP_SHARED_LIBRARY_CHECK)); + assertEquals("PCL[a];DLC[b]", DexoptUtils.encodeClassLoaderChain("PCL[a]", + "DLC[b]")); + assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]", + SKIP_SHARED_LIBRARY_CHECK)); + + try { + DexoptUtils.encodeClassLoaderChain("a", null); + fail(); // exception is expected + } catch (NullPointerException expected) {} + + try { + DexoptUtils.encodeClassLoaderChain(null, "b"); + fail(); // exception is expected + } catch (NullPointerException expected) {} + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index 2e99433149ea..69a148db8b63 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -21,6 +21,7 @@ import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import dalvik.system.VMRuntime; +import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -72,25 +73,25 @@ public class PackageDexUsageTests { String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); mFooBaseUser0 = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, isa, false, true); + fooCodeDir + "base.apk", 0, isa, false, true, fooPackageName); mFooSplit1User0 = new TestData(fooPackageName, - fooCodeDir + "split-1.apk", 0, isa, false, true); + fooCodeDir + "split-1.apk", 0, isa, false, true, fooPackageName); mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName, - fooCodeDir + "split-2.apk", 0, isa, true, true); + fooCodeDir + "split-2.apk", 0, isa, true, true, "used.by.other.com"); mFooSecondary1User0 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 0, isa, false, false); + fooDataDir + "sec-1.dex", 0, isa, false, false, fooPackageName); mFooSecondary1User1 = new TestData(fooPackageName, - fooDataDir + "sec-1.dex", 1, isa, false, false); + fooDataDir + "sec-1.dex", 1, isa, false, false, fooPackageName); mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName, - fooDataDir + "sec-2.dex", 0, isa, true, false); + fooDataDir + "sec-2.dex", 0, isa, true, false, "used.by.other.com"); mInvalidIsa = new TestData(fooPackageName, - fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true); + fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true, "INALID_USER"); String barPackageName = "com.google.bar"; String barCodeDir = "/data/app/com.google.bar/"; @@ -98,11 +99,11 @@ public class PackageDexUsageTests { String barDataDir1 = "/data/user/1/com.google.bar/"; mBarBaseUser0 = new TestData(barPackageName, - barCodeDir + "base.apk", 0, isa, false, true); + barCodeDir + "base.apk", 0, isa, false, true, barPackageName); mBarSecondary1User0 = new TestData(barPackageName, - barDataDir + "sec-1.dex", 0, isa, false, false); + barDataDir + "sec-1.dex", 0, isa, false, false, barPackageName); mBarSecondary2User1 = new TestData(barPackageName, - barDataDir1 + "sec-2.dex", 1, isa, false, false); + barDataDir1 + "sec-2.dex", 1, isa, false, false, barPackageName); } @Test @@ -249,7 +250,10 @@ public class PackageDexUsageTests { Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); packageToUsersMap.put(mBarSecondary2User1.mPackageName, new HashSet<>(Arrays.asList(mBarSecondary2User1.mOwnerUserId))); - mPackageDexUsage.syncData(packageToUsersMap); + Map<String, Set<String>> packageToCodePaths = new HashMap<>(); + packageToCodePaths.put(mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); + mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); // Assert that only user 1 files are there. assertPackageDexUsage(mBarBaseUser0, mBarSecondary2User1); @@ -319,7 +323,8 @@ public class PackageDexUsageTests { mFooSplit2UsedByOtherApps0.mOwnerUserId, mFooSplit2UsedByOtherApps0.mLoaderIsa, /*mIsUsedByOtherApps*/false, - mFooSplit2UsedByOtherApps0.mPrimaryOrSplit); + mFooSplit2UsedByOtherApps0.mPrimaryOrSplit, + mFooSplit2UsedByOtherApps0.mUsedBy); assertPackageDexUsage(noLongerUsedByOtherApps); } @@ -332,14 +337,223 @@ public class PackageDexUsageTests { assertFalse(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName)); } + @Test + public void testRecordDexFileUsers() { + PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); + Set<String> users = new HashSet<>(Arrays.asList( + new String[] {"another.package.1"})); + Set<String> usersExtra = new HashSet<>(Arrays.asList( + new String[] {"another.package.2", "another.package.3"})); + + assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); + + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); + + packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); + // Verify that the users were recorded. + Set<String> userAll = new HashSet<>(users); + userAll.addAll(usersExtra); + assertPackageDexUsage(packageDexUsageRecordUsers, userAll, mFooSplit2UsedByOtherApps0, + mFooSecondary1User0); + } + + @Test + public void testRecordDexFileUsersNotTheOwningPackage() { + PackageDexUsage packageDexUsageRecordUsers = new PackageDexUsage(); + Set<String> users = new HashSet<>(Arrays.asList( + new String[] {mFooSplit2UsedByOtherApps0.mPackageName})); + Set<String> usersExtra = new HashSet<>(Arrays.asList( + new String[] {"another.package.2", "another.package.3"})); + + assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooSplit2UsedByOtherApps0, usersExtra)); + + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, users)); + assertTrue(record(packageDexUsageRecordUsers, mFooSecondary1User0, usersExtra)); + + packageDexUsageRecordUsers = writeAndReadBack(packageDexUsageRecordUsers); + // Verify that only the non owning packages were recorded. + assertPackageDexUsage(packageDexUsageRecordUsers, usersExtra, mFooSplit2UsedByOtherApps0, + mFooSecondary1User0); + } + + @Test + public void testRecordClassLoaderContextVariableContext() { + // Record a secondary dex file. + assertTrue(record(mFooSecondary1User0)); + // Now update its context. + TestData fooSecondary1User0NewContext = mFooSecondary1User0.updateClassLoaderContext( + "PCL[new_context.dex]"); + assertTrue(record(fooSecondary1User0NewContext)); + + // Not check that the context was switch to variable. + TestData expectedContext = mFooSecondary1User0.updateClassLoaderContext( + PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT); + + assertPackageDexUsage(null, expectedContext); + writeAndReadBack(); + assertPackageDexUsage(null, expectedContext); + } + + @Test + public void testRecordClassLoaderContextUnsupportedContext() { + // Record a secondary dex file. + assertTrue(record(mFooSecondary1User0)); + // Now update its context. + TestData unsupportedContext = mFooSecondary1User0.updateClassLoaderContext( + PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT); + assertTrue(record(unsupportedContext)); + + assertPackageDexUsage(null, unsupportedContext); + writeAndReadBack(); + assertPackageDexUsage(null, unsupportedContext); + } + + @Test + public void testRecordClassLoaderContextTransitionFromUnknown() { + // Record a secondary dex file. + TestData unknownContext = mFooSecondary1User0.updateClassLoaderContext( + PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT); + assertTrue(record(unknownContext)); + + assertPackageDexUsage(null, unknownContext); + writeAndReadBack(); + assertPackageDexUsage(null, unknownContext); + + // Now update the secondary dex record with a class loader context. This simulates the + // version 2 to version 3 upgrade. + + assertTrue(record(mFooSecondary1User0)); + + assertPackageDexUsage(null, mFooSecondary1User0); + writeAndReadBack(); + assertPackageDexUsage(null, mFooSecondary1User0); + } + + @Test + public void testDexUsageClassLoaderContext() { + final boolean isUsedByOtherApps = false; + final int userId = 0; + PackageDexUsage.DexUseInfo validContext = new DexUseInfo(isUsedByOtherApps, userId, + "valid_context", "arm"); + assertFalse(validContext.isUnknownClassLoaderContext()); + assertFalse(validContext.isUnsupportedClassLoaderContext()); + assertFalse(validContext.isVariableClassLoaderContext()); + + PackageDexUsage.DexUseInfo unsupportedContext = new DexUseInfo(isUsedByOtherApps, userId, + PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT, "arm"); + assertFalse(unsupportedContext.isUnknownClassLoaderContext()); + assertTrue(unsupportedContext.isUnsupportedClassLoaderContext()); + assertFalse(unsupportedContext.isVariableClassLoaderContext()); + + PackageDexUsage.DexUseInfo variableContext = new DexUseInfo(isUsedByOtherApps, userId, + PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT, "arm"); + assertFalse(variableContext.isUnknownClassLoaderContext()); + assertFalse(variableContext.isUnsupportedClassLoaderContext()); + assertTrue(variableContext.isVariableClassLoaderContext()); + + PackageDexUsage.DexUseInfo unknownContext = new DexUseInfo(isUsedByOtherApps, userId, + PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT, "arm"); + assertTrue(unknownContext.isUnknownClassLoaderContext()); + assertFalse(unknownContext.isUnsupportedClassLoaderContext()); + assertFalse(unknownContext.isVariableClassLoaderContext()); + } + + @Test + public void testReadVersion1() { + String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); + // Equivalent to + // record(mFooSplit2UsedByOtherApps0); + // record(mFooSecondary1User0); + // record(mFooSecondary2UsedByOtherApps0); + // record(mBarBaseUser0); + // record(mBarSecondary1User0); + String content = "PACKAGE_MANAGER__PACKAGE_DEX_USAGE__1\n" + + "com.google.foo,1\n" + + "#/data/user/0/com.google.foo/sec-1.dex\n" + + "0,0," + isa + "\n" + + "#/data/user/0/com.google.foo/sec-2.dex\n" + + "0,1," + isa + "\n" + + "com.google.bar,0\n" + + "#/data/user/0/com.google.bar/sec-1.dex\n" + + "0,0," + isa + "\n"; + + PackageDexUsage packageDexUsage = new PackageDexUsage(); + try { + packageDexUsage.read(new StringReader(content)); + } catch (IOException e) { + fail(); + } + + // After the read we must sync the data to fill the missing information on the code paths. + Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); + Map<String, Set<String>> packageToCodePaths = new HashMap<>(); + + // Handle foo package. + packageToUsersMap.put(mFooSplit2UsedByOtherApps0.mPackageName, + new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mOwnerUserId))); + packageToCodePaths.put(mFooSplit2UsedByOtherApps0.mPackageName, + new HashSet<>(Arrays.asList(mFooSplit2UsedByOtherApps0.mDexFile, + mFooSplit1User0.mDexFile, mFooBaseUser0.mDexFile))); + // Handle bar package. + packageToUsersMap.put(mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mOwnerUserId))); + packageToCodePaths.put(mBarBaseUser0.mPackageName, + new HashSet<>(Arrays.asList(mBarBaseUser0.mDexFile))); + + // Sync the data. + packageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + + // Update the class loaders to unknown before asserting if needed. Before version 2 we + // didn't have any. + String unknown = PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT; + TestData fooBaseUser0 = mFooBaseUser0.updateClassLoaderContext(unknown); + TestData fooSplit1User0 = mFooSplit1User0.updateClassLoaderContext(unknown); + TestData fooSplit2UsedByOtherApps0 = + mFooSplit2UsedByOtherApps0.updateClassLoaderContext(unknown); + TestData fooSecondary1User0 = mFooSecondary1User0.updateClassLoaderContext(unknown); + TestData fooSecondary2UsedByOtherApps0 = + mFooSecondary2UsedByOtherApps0.updateClassLoaderContext(unknown); + TestData barBaseUser0 = mBarBaseUser0.updateClassLoaderContext(unknown); + TestData barSecondary1User0 = mBarSecondary1User0.updateClassLoaderContext(unknown); + + // Assert foo code paths. Note that we ignore the users during upgrade. + final Set<String> ignoredUsers = null; + assertPackageDexUsage(packageDexUsage, ignoredUsers, + fooSplit2UsedByOtherApps0, fooSecondary1User0, fooSecondary2UsedByOtherApps0); + // Because fooSplit2UsedByOtherApps0 is used by others, all the other code paths must + // share the same data. + assertPackageDexUsage(packageDexUsage, ignoredUsers, + fooSplit1User0.updateUseByOthers(true), + fooSecondary1User0, fooSecondary2UsedByOtherApps0); + assertPackageDexUsage(packageDexUsage, ignoredUsers, fooBaseUser0.updateUseByOthers(true), + fooSecondary1User0, fooSecondary2UsedByOtherApps0); + + // Assert bar code paths. Note that we ignore the users during upgrade. + assertPackageDexUsage(packageDexUsage, ignoredUsers, barBaseUser0, barSecondary1User0); + } + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { + assertPackageDexUsage(mPackageDexUsage, null, primary, secondaries); + } + + private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users, + TestData primary, TestData... secondaries) { String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; - boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps; - PackageUseInfo pInfo = mPackageDexUsage.getPackageUseInfo(packageName); + boolean primaryUsedByOtherApps = primary != null && primary.mUsedByOtherApps; + PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName); // Check package use info assertNotNull(pInfo); - assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps()); + if (primary != null) { + assertEquals(primaryUsedByOtherApps, pInfo.isUsedByOtherApps(primary.mDexFile)); + if (users != null) { + assertEquals(pInfo.getLoadingPackages(primary.mDexFile), users); + } + } + Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap(); assertEquals(secondaries.length, dexUseInfoMap.size()); @@ -351,24 +565,45 @@ public class PackageDexUsageTests { assertEquals(testData.mOwnerUserId, dInfo.getOwnerUserId()); assertEquals(1, dInfo.getLoaderIsas().size()); assertTrue(dInfo.getLoaderIsas().contains(testData.mLoaderIsa)); + if (users != null) { + assertEquals(dInfo.getLoadingPackages(), users); + } + + assertEquals(testData.mClassLoaderContext, dInfo.getClassLoaderContext()); } } private boolean record(TestData testData) { return mPackageDexUsage.record(testData.mPackageName, testData.mDexFile, - testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, - testData.mPrimaryOrSplit); + testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mPrimaryOrSplit, testData.mUsedBy, testData.mClassLoaderContext); + } + + private boolean record(PackageDexUsage packageDexUsage, TestData testData, Set<String> users) { + boolean result = true; + for (String user : users) { + result = result && packageDexUsage.record(testData.mPackageName, testData.mDexFile, + testData.mOwnerUserId, testData.mLoaderIsa, testData.mUsedByOtherApps, + testData.mPrimaryOrSplit, user, testData.mClassLoaderContext); + } + return result; } private void writeAndReadBack() { + mPackageDexUsage = writeAndReadBack(mPackageDexUsage); + } + + private PackageDexUsage writeAndReadBack(PackageDexUsage packageDexUsage) { try { StringWriter writer = new StringWriter(); - mPackageDexUsage.write(writer); + packageDexUsage.write(writer); - mPackageDexUsage = new PackageDexUsage(); - mPackageDexUsage.read(new StringReader(writer.toString())); + PackageDexUsage newPackageDexUsage = new PackageDexUsage(); + newPackageDexUsage.read(new StringReader(writer.toString())); + return newPackageDexUsage; } catch (IOException e) { fail("Unexpected IOException: " + e.getMessage()); + return null; } } @@ -379,16 +614,35 @@ public class PackageDexUsageTests { private final String mLoaderIsa; private final boolean mUsedByOtherApps; private final boolean mPrimaryOrSplit; + private final String mUsedBy; + private final String mClassLoaderContext; private TestData(String packageName, String dexFile, int ownerUserId, - String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit) { + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy) { + this(packageName, dexFile, ownerUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, + usedBy, "DefaultClassLoaderContextFor_" + dexFile); + } + private TestData(String packageName, String dexFile, int ownerUserId, + String loaderIsa, boolean isUsedByOtherApps, boolean primaryOrSplit, String usedBy, + String classLoaderContext) { mPackageName = packageName; mDexFile = dexFile; mOwnerUserId = ownerUserId; mLoaderIsa = loaderIsa; mUsedByOtherApps = isUsedByOtherApps; mPrimaryOrSplit = primaryOrSplit; + mUsedBy = usedBy; + mClassLoaderContext = classLoaderContext; } + private TestData updateClassLoaderContext(String newContext) { + return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, mUsedByOtherApps, + mPrimaryOrSplit, mUsedBy, newContext); + } + + private TestData updateUseByOthers(boolean newUsedByOthers) { + return new TestData(mPackageName, mDexFile, mOwnerUserId, mLoaderIsa, newUsedByOthers, + mPrimaryOrSplit, mUsedBy, mClassLoaderContext); + } } } diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 9f4fb85f64c4..21b11b058160 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -392,6 +392,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { res.videoBytes = stats[2]; res.imageBytes = stats[3]; res.appBytes = stats[4]; + res.obbBytes = stats[5]; return res; } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 221986395fd3..946d237f5ba6 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -902,7 +902,7 @@ public class UsbDeviceManager { updateCurrentAccessory(); } if (mBootCompleted) { - if (!mConnected) { + if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)) { // restore defaults when USB is disconnected setEnabledFunctions(null, !mAdbEnabled, false); } diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 6f970fa8e0b5..a81fba95c4ed 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -1449,6 +1449,12 @@ public abstract class ConnectionService extends Service { */ private void notifyCreateConnectionComplete(final String callId) { Log.i(this, "notifyCreateConnectionComplete %s", callId); + if (callId == null) { + // This could happen if the connection fails quickly and is removed from the + // ConnectionService before Telecom sends the create connection complete callback. + Log.w(this, "notifyCreateConnectionComplete: callId is null."); + return; + } onCreateConnectionComplete(findConnectionForAction(callId, "notifyCreateConnectionComplete")); } @@ -2177,7 +2183,7 @@ public abstract class ConnectionService extends Service { } private Connection findConnectionForAction(String callId, String action) { - if (mConnectionById.containsKey(callId)) { + if (callId != null && mConnectionById.containsKey(callId)) { return mConnectionById.get(callId); } Log.w(this, "%s - Cannot find Connection %s", action, callId); diff --git a/telecomm/java/android/telecom/DefaultDialerManager.java b/telecomm/java/android/telecom/DefaultDialerManager.java index cd652329d9f9..2a707c91ebe4 100644 --- a/telecomm/java/android/telecom/DefaultDialerManager.java +++ b/telecomm/java/android/telecom/DefaultDialerManager.java @@ -170,7 +170,7 @@ public class DefaultDialerManager { final Intent dialIntentWithTelScheme = new Intent(Intent.ACTION_DIAL); dialIntentWithTelScheme.setData(Uri.fromParts(PhoneAccount.SCHEME_TEL, "", null)); - return filterByIntent(context, packageNames, dialIntentWithTelScheme); + return filterByIntent(context, packageNames, dialIntentWithTelScheme, userId); } public static List<String> getInstalledDialerApplications(Context context) { @@ -204,17 +204,18 @@ public class DefaultDialerManager { * * @param context A valid context * @param packageNames List of package names to filter. + * @param userId The UserId * @return The filtered list. */ private static List<String> filterByIntent(Context context, List<String> packageNames, - Intent intent) { + Intent intent, int userId) { if (packageNames == null || packageNames.isEmpty()) { return new ArrayList<>(); } final List<String> result = new ArrayList<>(); final List<ResolveInfo> resolveInfoList = context.getPackageManager() - .queryIntentActivities(intent, 0); + .queryIntentActivitiesAsUser(intent, 0, userId); final int length = resolveInfoList.size(); for (int i = 0; i < length; i++) { final ActivityInfo info = resolveInfoList.get(i).activityInfo; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 91b2d520d806..35e31d19c960 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1508,6 +1508,13 @@ public class CarrierConfigManager { "boosted_lte_earfcns_string_array"; /** + * Determine whether to use only RSRP for the number of LTE signal bars. + * @hide + */ + public static final String KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL = + "use_only_rsrp_for_lte_signal_bar_bool"; + + /** * Key identifying if voice call barring notification is required to be shown to the user. * @hide */ @@ -1600,6 +1607,13 @@ public class CarrierConfigManager { public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY = "feature_access_codes_string_array"; + /** + * Determines if the carrier wants to identify high definition calls in the call log. + * @hide + */ + public static final String KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL = + "identify_high_definition_calls_in_call_log_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1862,6 +1876,7 @@ public class CarrierConfigManager { null); sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0); sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false); sDefaults.putInt(IMSI_KEY_EXPIRATION_DAYS_TIME_INT, IMSI_ENCRYPTION_DAYS_TIME_DISABLED); sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null); @@ -1872,6 +1887,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false); sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false); } /** diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java index b39b4c76ff04..ddc938e64841 100644 --- a/telephony/java/android/telephony/CellIdentityCdma.java +++ b/telephony/java/android/telephony/CellIdentityCdma.java @@ -19,6 +19,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import android.text.TextUtils; import java.util.Objects; @@ -50,6 +51,10 @@ public final class CellIdentityCdma implements Parcelable { * to +90 degrees). */ private final int mLatitude; + // long alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaLong; + // short alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaShort; /** * @hide @@ -60,6 +65,8 @@ public final class CellIdentityCdma implements Parcelable { mBasestationId = Integer.MAX_VALUE; mLongitude = Integer.MAX_VALUE; mLatitude = Integer.MAX_VALUE; + mAlphaLong = null; + mAlphaShort = null; } /** @@ -75,19 +82,37 @@ public final class CellIdentityCdma implements Parcelable { * @hide */ public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat) { + this(nid, sid, bid, lon, lat, null, null); + } + + /** + * public constructor + * @param nid Network Id 0..65535 + * @param sid CDMA System Id 0..32767 + * @param bid Base Station Id 0..65535 + * @param lon Longitude is a decimal number ranges from -2592000 + * to 2592000 + * @param lat Latitude is a decimal number ranges from -1296000 + * to 1296000 + * @param alphal long alpha Operator Name String or Enhanced Operator Name String + * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * + * @hide + */ + public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat, String alphal, + String alphas) { mNetworkId = nid; mSystemId = sid; mBasestationId = bid; mLongitude = lon; mLatitude = lat; + mAlphaLong = alphal; + mAlphaShort = alphas; } private CellIdentityCdma(CellIdentityCdma cid) { - mNetworkId = cid.mNetworkId; - mSystemId = cid.mSystemId; - mBasestationId = cid.mBasestationId; - mLongitude = cid.mLongitude; - mLatitude = cid.mLatitude; + this(cid.mNetworkId, cid.mSystemId, cid.mBasestationId, cid.mLongitude, cid.mLatitude, + cid.mAlphaLong, cid.mAlphaShort); } CellIdentityCdma copy() { @@ -137,9 +162,26 @@ public final class CellIdentityCdma implements Parcelable { return mLatitude; } + /** + * @return The long alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaLong() { + return mAlphaLong; + } + + /** + * @return The short alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaShort() { + return mAlphaShort; + } + @Override public int hashCode() { - return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude); + return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude, + mAlphaLong, mAlphaShort); } @Override @@ -153,11 +195,14 @@ public final class CellIdentityCdma implements Parcelable { } CellIdentityCdma o = (CellIdentityCdma) other; + return mNetworkId == o.mNetworkId && mSystemId == o.mSystemId && mBasestationId == o.mBasestationId && mLatitude == o.mLatitude && - mLongitude == o.mLongitude; + mLongitude == o.mLongitude && + TextUtils.equals(mAlphaLong, o.mAlphaLong) && + TextUtils.equals(mAlphaShort, o.mAlphaShort); } @Override @@ -168,6 +213,8 @@ public final class CellIdentityCdma implements Parcelable { sb.append(" mBasestationId="); sb.append(mBasestationId); sb.append(" mLongitude="); sb.append(mLongitude); sb.append(" mLatitude="); sb.append(mLatitude); + sb.append(" mAlphaLong="); sb.append(mAlphaLong); + sb.append(" mAlphaShort="); sb.append(mAlphaShort); sb.append("}"); return sb.toString(); @@ -188,15 +235,15 @@ public final class CellIdentityCdma implements Parcelable { dest.writeInt(mBasestationId); dest.writeInt(mLongitude); dest.writeInt(mLatitude); + dest.writeString(mAlphaLong); + dest.writeString(mAlphaShort); } /** Construct from Parcel, type has already been processed */ private CellIdentityCdma(Parcel in) { - mNetworkId = in.readInt(); - mSystemId = in.readInt(); - mBasestationId = in.readInt(); - mLongitude = in.readInt(); - mLatitude = in.readInt(); + this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readInt(), + in.readString(), in.readString()); + if (DBG) log("CellIdentityCdma(Parcel): " + toString()); } diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java index ec008e285bb2..6276626afae2 100644 --- a/telephony/java/android/telephony/CellIdentityGsm.java +++ b/telephony/java/android/telephony/CellIdentityGsm.java @@ -19,6 +19,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import android.text.TextUtils; import java.util.Objects; @@ -30,10 +31,6 @@ public final class CellIdentityGsm implements Parcelable { private static final String LOG_TAG = "CellIdentityGsm"; private static final boolean DBG = false; - // 3-digit Mobile Country Code, 0..999 - private final int mMcc; - // 2 or 3-digit Mobile Network Code, 0..999 - private final int mMnc; // 16-bit Location Area Code, 0..65535 private final int mLac; // 16-bit GSM Cell Identity described in TS 27.007, 0..65535 @@ -42,17 +39,27 @@ public final class CellIdentityGsm implements Parcelable { private final int mArfcn; // 6-bit Base Station Identity Code private final int mBsic; + // 3-digit Mobile Country Code in string format + private final String mMccStr; + // 2 or 3-digit Mobile Network Code in string format + private final String mMncStr; + // long alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaLong; + // short alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaShort; /** * @hide */ public CellIdentityGsm() { - mMcc = Integer.MAX_VALUE; - mMnc = Integer.MAX_VALUE; mLac = Integer.MAX_VALUE; mCid = Integer.MAX_VALUE; mArfcn = Integer.MAX_VALUE; mBsic = Integer.MAX_VALUE; + mMccStr = null; + mMncStr = null; + mAlphaLong = null; + mAlphaShort = null; } /** * public constructor @@ -64,7 +71,8 @@ public final class CellIdentityGsm implements Parcelable { * @hide */ public CellIdentityGsm (int mcc, int mnc, int lac, int cid) { - this(mcc, mnc, lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE); + this(lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE, + String.valueOf(mcc), String.valueOf(mnc), null, null); } /** @@ -79,39 +87,81 @@ public final class CellIdentityGsm implements Parcelable { * @hide */ public CellIdentityGsm (int mcc, int mnc, int lac, int cid, int arfcn, int bsic) { - mMcc = mcc; - mMnc = mnc; + this(lac, cid, arfcn, bsic, String.valueOf(mcc), String.valueOf(mnc), null, null); + } + + /** + * public constructor + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity + * @param arfcn 16-bit GSM Absolute RF Channel Number + * @param bsic 6-bit Base Station Identity Code + * @param mccStr 3-digit Mobile Country Code in string format + * @param mncStr 2 or 3-digit Mobile Network Code in string format + * @param alphal long alpha Operator Name String or Enhanced Operator Name String + * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * + * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is + * not a 2 or 3-digit code. + * + * @hide + */ + public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr, + String mncStr, String alphal, String alphas) { mLac = lac; mCid = cid; mArfcn = arfcn; - mBsic = bsic; + // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator + // for inbound parcels + mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic; + + if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { + mMccStr = mccStr; + } else if (mccStr.isEmpty()) { + // If the mccStr parsed from Parcel is empty, set it as null. + mMccStr = null; + } else { + throw new IllegalArgumentException("invalid MCC format"); + } + + if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { + mMncStr = mncStr; + } else if (mncStr.isEmpty()) { + // If the mncStr parsed from Parcel is empty, set it as null. + mMncStr = null; + } else { + throw new IllegalArgumentException("invalid MNC format"); + } + + mAlphaLong = alphal; + mAlphaShort = alphas; } private CellIdentityGsm(CellIdentityGsm cid) { - mMcc = cid.mMcc; - mMnc = cid.mMnc; - mLac = cid.mLac; - mCid = cid.mCid; - mArfcn = cid.mArfcn; - mBsic = cid.mBsic; + this(cid.mLac, cid.mCid, cid.mArfcn, cid.mBsic, cid.mMccStr, + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); } CellIdentityGsm copy() { - return new CellIdentityGsm(this); + return new CellIdentityGsm(this); } /** * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMccStr} instead. */ + @Deprecated public int getMcc() { - return mMcc; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; } /** * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMncStr} instead. */ + @Deprecated public int getMnc() { - return mMnc; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; } /** @@ -144,6 +194,43 @@ public final class CellIdentityGsm implements Parcelable { return mBsic; } + /** + * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown + */ + public String getMobileNetworkOperator() { + return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr; + } + + /** + * @return Mobile Country Code in string format, null if unknown + */ + public String getMccStr() { + return mMccStr; + } + + /** + * @return Mobile Network Code in string format, null if unknown + */ + public String getMncStr() { + return mMncStr; + } + + /** + * @return The long alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaLong() { + return mAlphaLong; + } + + /** + * @return The short alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaShort() { + return mAlphaShort; + } + /** * @return Integer.MAX_VALUE, undefined for GSM @@ -155,7 +242,7 @@ public final class CellIdentityGsm implements Parcelable { @Override public int hashCode() { - return Objects.hash(mMcc, mMnc, mLac, mCid); + return Objects.hash(mMccStr, mMncStr, mLac, mCid, mAlphaLong, mAlphaShort); } @Override @@ -169,23 +256,27 @@ public final class CellIdentityGsm implements Parcelable { } CellIdentityGsm o = (CellIdentityGsm) other; - return mMcc == o.mMcc && - mMnc == o.mMnc && - mLac == o.mLac && + return mLac == o.mLac && mCid == o.mCid && mArfcn == o.mArfcn && - mBsic == o.mBsic; + mBsic == o.mBsic && + TextUtils.equals(mMccStr, o.mMccStr) && + TextUtils.equals(mMncStr, o.mMncStr) && + TextUtils.equals(mAlphaLong, o.mAlphaLong) && + TextUtils.equals(mAlphaShort, o.mAlphaShort); } @Override public String toString() { StringBuilder sb = new StringBuilder("CellIdentityGsm:{"); - sb.append(" mMcc=").append(mMcc); - sb.append(" mMnc=").append(mMnc); sb.append(" mLac=").append(mLac); sb.append(" mCid=").append(mCid); sb.append(" mArfcn=").append(mArfcn); sb.append(" mBsic=").append("0x").append(Integer.toHexString(mBsic)); + sb.append(" mMcc=").append(mMccStr); + sb.append(" mMnc=").append(mMncStr); + sb.append(" mAlphaLong=").append(mAlphaLong); + sb.append(" mAlphaShort=").append(mAlphaShort); sb.append("}"); return sb.toString(); @@ -201,26 +292,20 @@ public final class CellIdentityGsm implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { if (DBG) log("writeToParcel(Parcel, int): " + toString()); - dest.writeInt(mMcc); - dest.writeInt(mMnc); dest.writeInt(mLac); dest.writeInt(mCid); dest.writeInt(mArfcn); dest.writeInt(mBsic); + dest.writeString(mMccStr); + dest.writeString(mMncStr); + dest.writeString(mAlphaLong); + dest.writeString(mAlphaShort); } /** Construct from Parcel, type has already been processed */ private CellIdentityGsm(Parcel in) { - mMcc = in.readInt(); - mMnc = in.readInt(); - mLac = in.readInt(); - mCid = in.readInt(); - mArfcn = in.readInt(); - int bsic = in.readInt(); - // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator - // for inbound parcels - if (bsic == 0xFF) bsic = Integer.MAX_VALUE; - mBsic = bsic; + this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(), + in.readString(), in.readString(), in.readString()); if (DBG) log("CellIdentityGsm(Parcel): " + toString()); } @@ -229,16 +314,16 @@ public final class CellIdentityGsm implements Parcelable { @SuppressWarnings("hiding") public static final Creator<CellIdentityGsm> CREATOR = new Creator<CellIdentityGsm>() { - @Override - public CellIdentityGsm createFromParcel(Parcel in) { - return new CellIdentityGsm(in); - } + @Override + public CellIdentityGsm createFromParcel(Parcel in) { + return new CellIdentityGsm(in); + } - @Override - public CellIdentityGsm[] newArray(int size) { - return new CellIdentityGsm[size]; - } - }; + @Override + public CellIdentityGsm[] newArray(int size) { + return new CellIdentityGsm[size]; + } + }; /** * log @@ -246,4 +331,4 @@ public final class CellIdentityGsm implements Parcelable { private static void log(String s) { Rlog.w(LOG_TAG, s); } -} +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index ce7438354a50..74d2966b25bc 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -19,6 +19,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import android.text.TextUtils; import java.util.Objects; @@ -30,10 +31,6 @@ public final class CellIdentityLte implements Parcelable { private static final String LOG_TAG = "CellIdentityLte"; private static final boolean DBG = false; - // 3-digit Mobile Country Code, 0..999 - private final int mMcc; - // 2 or 3-digit Mobile Network Code, 0..999 - private final int mMnc; // 28-bit cell identity private final int mCi; // physical cell id 0..503 @@ -42,17 +39,27 @@ public final class CellIdentityLte implements Parcelable { private final int mTac; // 18-bit Absolute RF Channel Number private final int mEarfcn; + // 3-digit Mobile Country Code in string format + private final String mMccStr; + // 2 or 3-digit Mobile Network Code in string format + private final String mMncStr; + // long alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaLong; + // short alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaShort; /** * @hide */ public CellIdentityLte() { - mMcc = Integer.MAX_VALUE; - mMnc = Integer.MAX_VALUE; mCi = Integer.MAX_VALUE; mPci = Integer.MAX_VALUE; mTac = Integer.MAX_VALUE; mEarfcn = Integer.MAX_VALUE; + mMccStr = null; + mMncStr = null; + mAlphaLong = null; + mAlphaShort = null; } /** @@ -66,7 +73,7 @@ public final class CellIdentityLte implements Parcelable { * @hide */ public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac) { - this(mcc, mnc, ci, pci, tac, Integer.MAX_VALUE); + this(ci, pci, tac, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), null, null); } /** @@ -81,21 +88,57 @@ public final class CellIdentityLte implements Parcelable { * @hide */ public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac, int earfcn) { - mMcc = mcc; - mMnc = mnc; + this(ci, pci, tac, earfcn, String.valueOf(mcc), String.valueOf(mnc), null, null); + } + + /** + * + * @param ci 28-bit Cell Identity + * @param pci Physical Cell Id 0..503 + * @param tac 16-bit Tracking Area Code + * @param earfcn 18-bit LTE Absolute RF Channel Number + * @param mccStr 3-digit Mobile Country Code in string format + * @param mncStr 2 or 3-digit Mobile Network Code in string format + * @param alphal long alpha Operator Name String or Enhanced Operator Name String + * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * + * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is + * not a 2 or 3-digit code. + * + * @hide + */ + public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr, + String mncStr, String alphal, String alphas) { mCi = ci; mPci = pci; mTac = tac; mEarfcn = earfcn; + + if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { + mMccStr = mccStr; + } else if (mccStr.isEmpty()) { + // If the mccStr parsed from Parcel is empty, set it as null. + mMccStr = null; + } else { + throw new IllegalArgumentException("invalid MCC format"); + } + + if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { + mMncStr = mncStr; + } else if (mncStr.isEmpty()) { + // If the mncStr parsed from Parcel is empty, set it as null. + mMncStr = null; + } else { + throw new IllegalArgumentException("invalid MNC format"); + } + + mAlphaLong = alphal; + mAlphaShort = alphas; } private CellIdentityLte(CellIdentityLte cid) { - mMcc = cid.mMcc; - mMnc = cid.mMnc; - mCi = cid.mCi; - mPci = cid.mPci; - mTac = cid.mTac; - mEarfcn = cid.mEarfcn; + this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mMccStr, + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); } CellIdentityLte copy() { @@ -104,16 +147,20 @@ public final class CellIdentityLte implements Parcelable { /** * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMccStr} instead. */ + @Deprecated public int getMcc() { - return mMcc; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; } /** * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMncStr} instead. */ + @Deprecated public int getMnc() { - return mMnc; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; } /** @@ -144,9 +191,46 @@ public final class CellIdentityLte implements Parcelable { return mEarfcn; } + /** + * @return Mobile Country Code in string format, null if unknown + */ + public String getMccStr() { + return mMccStr; + } + + /** + * @return Mobile Network Code in string format, null if unknown + */ + public String getMncStr() { + return mMncStr; + } + + /** + * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown + */ + public String getMobileNetworkOperator() { + return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr; + } + + /** + * @return The long alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaLong() { + return mAlphaLong; + } + + /** + * @return The short alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaShort() { + return mAlphaShort; + } + @Override public int hashCode() { - return Objects.hash(mMcc, mMnc, mCi, mPci, mTac); + return Objects.hash(mMccStr, mMncStr, mCi, mPci, mTac, mAlphaLong, mAlphaShort); } @Override @@ -160,23 +244,27 @@ public final class CellIdentityLte implements Parcelable { } CellIdentityLte o = (CellIdentityLte) other; - return mMcc == o.mMcc && - mMnc == o.mMnc && - mCi == o.mCi && + return mCi == o.mCi && mPci == o.mPci && mTac == o.mTac && - mEarfcn == o.mEarfcn; + mEarfcn == o.mEarfcn && + TextUtils.equals(mMccStr, o.mMccStr) && + TextUtils.equals(mMncStr, o.mMncStr) && + TextUtils.equals(mAlphaLong, o.mAlphaLong) && + TextUtils.equals(mAlphaShort, o.mAlphaShort); } @Override public String toString() { StringBuilder sb = new StringBuilder("CellIdentityLte:{"); - sb.append(" mMcc="); sb.append(mMcc); - sb.append(" mMnc="); sb.append(mMnc); sb.append(" mCi="); sb.append(mCi); sb.append(" mPci="); sb.append(mPci); sb.append(" mTac="); sb.append(mTac); sb.append(" mEarfcn="); sb.append(mEarfcn); + sb.append(" mMcc="); sb.append(mMccStr); + sb.append(" mMnc="); sb.append(mMncStr); + sb.append(" mAlphaLong="); sb.append(mAlphaLong); + sb.append(" mAlphaShort="); sb.append(mAlphaShort); sb.append("}"); return sb.toString(); @@ -192,22 +280,21 @@ public final class CellIdentityLte implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { if (DBG) log("writeToParcel(Parcel, int): " + toString()); - dest.writeInt(mMcc); - dest.writeInt(mMnc); dest.writeInt(mCi); dest.writeInt(mPci); dest.writeInt(mTac); dest.writeInt(mEarfcn); + dest.writeString(mMccStr); + dest.writeString(mMncStr); + dest.writeString(mAlphaLong); + dest.writeString(mAlphaShort); } /** Construct from Parcel, type has already been processed */ private CellIdentityLte(Parcel in) { - mMcc = in.readInt(); - mMnc = in.readInt(); - mCi = in.readInt(); - mPci = in.readInt(); - mTac = in.readInt(); - mEarfcn = in.readInt(); + this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(), + in.readString(), in.readString(), in.readString()); + if (DBG) log("CellIdentityLte(Parcel): " + toString()); } @@ -215,16 +302,16 @@ public final class CellIdentityLte implements Parcelable { @SuppressWarnings("hiding") public static final Creator<CellIdentityLte> CREATOR = new Creator<CellIdentityLte>() { - @Override - public CellIdentityLte createFromParcel(Parcel in) { - return new CellIdentityLte(in); - } + @Override + public CellIdentityLte createFromParcel(Parcel in) { + return new CellIdentityLte(in); + } - @Override - public CellIdentityLte[] newArray(int size) { - return new CellIdentityLte[size]; - } - }; + @Override + public CellIdentityLte[] newArray(int size) { + return new CellIdentityLte[size]; + } + }; /** * log @@ -232,4 +319,4 @@ public final class CellIdentityLte implements Parcelable { private static void log(String s) { Rlog.w(LOG_TAG, s); } -} +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index 0d13efd279c9..51b11aa82b9a 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -19,6 +19,7 @@ package android.telephony; import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import android.text.TextUtils; import java.util.Objects; @@ -30,10 +31,6 @@ public final class CellIdentityWcdma implements Parcelable { private static final String LOG_TAG = "CellIdentityWcdma"; private static final boolean DBG = false; - // 3-digit Mobile Country Code, 0..999 - private final int mMcc; - // 2 or 3-digit Mobile Network Code, 0..999 - private final int mMnc; // 16-bit Location Area Code, 0..65535 private final int mLac; // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455 @@ -42,17 +39,27 @@ public final class CellIdentityWcdma implements Parcelable { private final int mPsc; // 16-bit UMTS Absolute RF Channel Number private final int mUarfcn; + // 3-digit Mobile Country Code in string format + private final String mMccStr; + // 2 or 3-digit Mobile Network Code in string format + private final String mMncStr; + // long alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaLong; + // short alpha Operator Name String or Enhanced Operator Name String + private final String mAlphaShort; /** * @hide */ public CellIdentityWcdma() { - mMcc = Integer.MAX_VALUE; - mMnc = Integer.MAX_VALUE; mLac = Integer.MAX_VALUE; mCid = Integer.MAX_VALUE; mPsc = Integer.MAX_VALUE; mUarfcn = Integer.MAX_VALUE; + mMccStr = null; + mMncStr = null; + mAlphaLong = null; + mAlphaShort = null; } /** * public constructor @@ -65,7 +72,8 @@ public final class CellIdentityWcdma implements Parcelable { * @hide */ public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) { - this(mcc, mnc, lac, cid, psc, Integer.MAX_VALUE); + this(lac, cid, psc, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), + null, null); } /** @@ -80,39 +88,79 @@ public final class CellIdentityWcdma implements Parcelable { * @hide */ public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc, int uarfcn) { - mMcc = mcc; - mMnc = mnc; + this(lac, cid, psc, uarfcn, String.valueOf(mcc), String.valueOf(mnc), null, null); + } + + /** + * public constructor + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 28-bit UMTS Cell Identity + * @param psc 9-bit UMTS Primary Scrambling Code + * @param uarfcn 16-bit UMTS Absolute RF Channel Number + * @param mccStr 3-digit Mobile Country Code in string format + * @param mncStr 2 or 3-digit Mobile Network Code in string format + * @param alphal long alpha Operator Name String or Enhanced Operator Name String + * @param alphas short alpha Operator Name String or Enhanced Operator Name String + * + * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is + * not a 2 or 3-digit code. + * + * @hide + */ + public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn, + String mccStr, String mncStr, String alphal, String alphas) { mLac = lac; mCid = cid; mPsc = psc; mUarfcn = uarfcn; + + if (mccStr == null || mccStr.matches("^[0-9]{3}$")) { + mMccStr = mccStr; + } else if (mccStr.isEmpty()) { + // If the mccStr parsed from Parcel is empty, set it as null. + mMccStr = null; + } else { + throw new IllegalArgumentException("invalid MCC format"); + } + + if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) { + mMncStr = mncStr; + } else if (mncStr.isEmpty()) { + // If the mncStr parsed from Parcel is empty, set it as null. + mMncStr = null; + } else { + throw new IllegalArgumentException("invalid MNC format"); + } + + mAlphaLong = alphal; + mAlphaShort = alphas; } private CellIdentityWcdma(CellIdentityWcdma cid) { - mMcc = cid.mMcc; - mMnc = cid.mMnc; - mLac = cid.mLac; - mCid = cid.mCid; - mPsc = cid.mPsc; - mUarfcn = cid.mUarfcn; + this(cid.mLac, cid.mCid, cid.mPsc, cid.mUarfcn, cid.mMccStr, + cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort); } CellIdentityWcdma copy() { - return new CellIdentityWcdma(this); + return new CellIdentityWcdma(this); } /** * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMccStr} instead. */ + @Deprecated public int getMcc() { - return mMcc; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; } /** * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @deprecated Use {@link #getMncStr} instead. */ + @Deprecated public int getMnc() { - return mMnc; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; } /** @@ -138,9 +186,46 @@ public final class CellIdentityWcdma implements Parcelable { return mPsc; } + /** + * @return Mobile Country Code in string version, null if unknown + */ + public String getMccStr() { + return mMccStr; + } + + /** + * @return Mobile Network Code in string version, null if unknown + */ + public String getMncStr() { + return mMncStr; + } + + /** + * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown + */ + public String getMobileNetworkOperator() { + return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr; + } + + /** + * @return The long alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaLong() { + return mAlphaLong; + } + + /** + * @return The short alpha tag associated with the current scan result (may be the operator + * name string or extended operator name string). May be null if unknown. + */ + public CharSequence getOperatorAlphaShort() { + return mAlphaShort; + } + @Override public int hashCode() { - return Objects.hash(mMcc, mMnc, mLac, mCid, mPsc); + return Objects.hash(mMccStr, mMncStr, mLac, mCid, mPsc, mAlphaLong, mAlphaShort); } /** @@ -161,23 +246,27 @@ public final class CellIdentityWcdma implements Parcelable { } CellIdentityWcdma o = (CellIdentityWcdma) other; - return mMcc == o.mMcc && - mMnc == o.mMnc && - mLac == o.mLac && + return mLac == o.mLac && mCid == o.mCid && mPsc == o.mPsc && - mUarfcn == o.mUarfcn; + mUarfcn == o.mUarfcn && + TextUtils.equals(mMccStr, o.mMccStr) && + TextUtils.equals(mMncStr, o.mMncStr) && + TextUtils.equals(mAlphaLong, o.mAlphaLong) && + TextUtils.equals(mAlphaShort, o.mAlphaShort); } @Override public String toString() { StringBuilder sb = new StringBuilder("CellIdentityWcdma:{"); - sb.append(" mMcc=").append(mMcc); - sb.append(" mMnc=").append(mMnc); sb.append(" mLac=").append(mLac); sb.append(" mCid=").append(mCid); sb.append(" mPsc=").append(mPsc); sb.append(" mUarfcn=").append(mUarfcn); + sb.append(" mMcc=").append(mMccStr); + sb.append(" mMnc=").append(mMncStr); + sb.append(" mAlphaLong=").append(mAlphaLong); + sb.append(" mAlphaShort=").append(mAlphaShort); sb.append("}"); return sb.toString(); @@ -193,22 +282,21 @@ public final class CellIdentityWcdma implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { if (DBG) log("writeToParcel(Parcel, int): " + toString()); - dest.writeInt(mMcc); - dest.writeInt(mMnc); dest.writeInt(mLac); dest.writeInt(mCid); dest.writeInt(mPsc); dest.writeInt(mUarfcn); + dest.writeString(mMccStr); + dest.writeString(mMncStr); + dest.writeString(mAlphaLong); + dest.writeString(mAlphaShort); } /** Construct from Parcel, type has already been processed */ private CellIdentityWcdma(Parcel in) { - mMcc = in.readInt(); - mMnc = in.readInt(); - mLac = in.readInt(); - mCid = in.readInt(); - mPsc = in.readInt(); - mUarfcn = in.readInt(); + this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(), + in.readString(), in.readString(), in.readString()); + if (DBG) log("CellIdentityWcdma(Parcel): " + toString()); } @@ -216,16 +304,16 @@ public final class CellIdentityWcdma implements Parcelable { @SuppressWarnings("hiding") public static final Creator<CellIdentityWcdma> CREATOR = new Creator<CellIdentityWcdma>() { - @Override - public CellIdentityWcdma createFromParcel(Parcel in) { - return new CellIdentityWcdma(in); - } + @Override + public CellIdentityWcdma createFromParcel(Parcel in) { + return new CellIdentityWcdma(in); + } - @Override - public CellIdentityWcdma[] newArray(int size) { - return new CellIdentityWcdma[size]; - } - }; + @Override + public CellIdentityWcdma[] newArray(int size) { + return new CellIdentityWcdma[size]; + } + }; /** * log @@ -233,4 +321,4 @@ public final class CellIdentityWcdma implements Parcelable { private static void log(String s) { Rlog.w(LOG_TAG, s); } -} +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java index 9a9877a88517..f392570ecb29 100644 --- a/telephony/java/android/telephony/MbmsDownloadSession.java +++ b/telephony/java/android/telephony/MbmsDownloadSession.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -74,6 +75,14 @@ public class MbmsDownloadSession implements AutoCloseable { "android.telephony.action.EmbmsDownload"; /** + * Metadata key that specifies the component name of the service to bind to for file-download. + * @hide + */ + @TestApi + public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = + "mbms-download-service-override"; + + /** * Integer extra that Android will attach to the intent supplied via * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)} * Indicates the result code of the download. One of diff --git a/telephony/java/android/telephony/MbmsStreamingSession.java b/telephony/java/android/telephony/MbmsStreamingSession.java index a8c46079148c..fb2ff7b178b1 100644 --- a/telephony/java/android/telephony/MbmsStreamingSession.java +++ b/telephony/java/android/telephony/MbmsStreamingSession.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; @@ -62,6 +63,14 @@ public class MbmsStreamingSession implements AutoCloseable { public static final String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming"; + /** + * Metadata key that specifies the component name of the service to bind to for file-download. + * @hide + */ + @TestApi + public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = + "mbms-streaming-service-override"; + private static AtomicBoolean sIsInitialized = new AtomicBoolean(false); private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null); diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index c8b4776522c6..de02de7ba17d 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -68,6 +68,7 @@ public class SignalStrength implements Parcelable { private int mTdScdmaRscp; private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult + private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar. /** * Create a new SignalStrength from a intent notifier Bundle @@ -108,6 +109,7 @@ public class SignalStrength implements Parcelable { mLteRsrpBoost = 0; mTdScdmaRscp = INVALID; isGsm = true; + mUseOnlyRsrpForLteLevel = false; } /** @@ -134,6 +136,7 @@ public class SignalStrength implements Parcelable { mLteRsrpBoost = 0; mTdScdmaRscp = INVALID; isGsm = gsmFlag; + mUseOnlyRsrpForLteLevel = false; } /** @@ -145,10 +148,10 @@ public class SignalStrength implements Parcelable { int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr, int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, - int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) { + int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag, boolean lteLevelBaseOnRsrp) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, - lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag); + lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag, lteLevelBaseOnRsrp); mTdScdmaRscp = tdScdmaRscp; } @@ -164,7 +167,7 @@ public class SignalStrength implements Parcelable { int tdScdmaRscp, boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, - lteRsrq, lteRssnr, lteCqi, 0, gsmFlag); + lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false); mTdScdmaRscp = tdScdmaRscp; } @@ -180,7 +183,7 @@ public class SignalStrength implements Parcelable { boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, - lteRsrq, lteRssnr, lteCqi, 0, gsmFlag); + lteRsrq, lteRssnr, lteCqi, 0, gsmFlag, false); } /** @@ -194,7 +197,7 @@ public class SignalStrength implements Parcelable { boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, 99, INVALID, - INVALID, INVALID, INVALID, 0, gsmFlag); + INVALID, INVALID, INVALID, 0, gsmFlag, false); } /** @@ -228,7 +231,7 @@ public class SignalStrength implements Parcelable { boolean gsm) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, 99, INVALID, - INVALID, INVALID, INVALID, 0, gsm); + INVALID, INVALID, INVALID, 0, gsm, false); } /** @@ -248,6 +251,7 @@ public class SignalStrength implements Parcelable { * @param lteCqi * @param lteRsrpBoost * @param gsm + * @param useOnlyRsrpForLteLevel * * @hide */ @@ -255,7 +259,7 @@ public class SignalStrength implements Parcelable { int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr, int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, - int lteRsrpBoost, boolean gsm) { + int lteRsrpBoost, boolean gsm, boolean useOnlyRsrpForLteLevel) { mGsmSignalStrength = gsmSignalStrength; mGsmBitErrorRate = gsmBitErrorRate; mCdmaDbm = cdmaDbm; @@ -271,6 +275,7 @@ public class SignalStrength implements Parcelable { mLteRsrpBoost = lteRsrpBoost; mTdScdmaRscp = INVALID; isGsm = gsm; + mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel; if (DBG) log("initialize: " + toString()); } @@ -293,6 +298,7 @@ public class SignalStrength implements Parcelable { mLteRsrpBoost = s.mLteRsrpBoost; mTdScdmaRscp = s.mTdScdmaRscp; isGsm = s.isGsm; + mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel; } /** @@ -318,6 +324,7 @@ public class SignalStrength implements Parcelable { mLteRsrpBoost = in.readInt(); mTdScdmaRscp = in.readInt(); isGsm = (in.readInt() != 0); + mUseOnlyRsrpForLteLevel = (in.readInt() != 0); } /** @@ -366,6 +373,7 @@ public class SignalStrength implements Parcelable { out.writeInt(mLteRsrpBoost); out.writeInt(mTdScdmaRscp); out.writeInt(isGsm ? 1 : 0); + out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0); } /** @@ -449,6 +457,17 @@ public class SignalStrength implements Parcelable { } /** + * @param useOnlyRsrpForLteLevel true if it uses only RSRP for the number of LTE signal bar, + * otherwise false. + * + * Used by phone to use only RSRP or not for the number of LTE signal bar. + * @hide + */ + public void setUseOnlyRsrpForLteLevel(boolean useOnlyRsrpForLteLevel) { + mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel; + } + + /** * @param lteRsrpBoost - signal strength offset * * Used by phone to set the lte signal strength offset which will be @@ -835,6 +854,13 @@ public class SignalStrength implements Parcelable { } } + if (useOnlyRsrpForLteLevel()) { + log("getLTELevel - rsrp = " + rsrpIconLevel); + if (rsrpIconLevel != -1) { + return rsrpIconLevel; + } + } + /* * Values are -200 dB to +300 (SNR*10dB) RS_SNR >= 13.0 dB =>4 bars 4.5 * dB <= RS_SNR < 13.0 dB => 3 bars 1.0 dB <= RS_SNR < 4.5 dB => 2 bars @@ -915,6 +941,15 @@ public class SignalStrength implements Parcelable { } /** + * @return true if it uses only RSRP for the number of LTE signal bar, otherwise false. + * + * @hide + */ + public boolean useOnlyRsrpForLteLevel() { + return this.mUseOnlyRsrpForLteLevel; + } + + /** * @return get TD_SCDMA dbm * * @hide @@ -974,7 +1009,8 @@ public class SignalStrength implements Parcelable { + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum) + (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum) + (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum) - + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)); + + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0) + + (mUseOnlyRsrpForLteLevel ? 1 : 0)); } /** @@ -1008,7 +1044,8 @@ public class SignalStrength implements Parcelable { && mLteCqi == s.mLteCqi && mLteRsrpBoost == s.mLteRsrpBoost && mTdScdmaRscp == s.mTdScdmaRscp - && isGsm == s.isGsm); + && isGsm == s.isGsm + && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel); } /** @@ -1031,7 +1068,9 @@ public class SignalStrength implements Parcelable { + " " + mLteCqi + " " + mLteRsrpBoost + " " + mTdScdmaRscp - + " " + (isGsm ? "gsm|lte" : "cdma")); + + " " + (isGsm ? "gsm|lte" : "cdma") + + " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" : + "use_rsrp_and_rssnr_for_lte_level")); } /** Returns the signal strength related to GSM. */ @@ -1086,6 +1125,7 @@ public class SignalStrength implements Parcelable { mLteRsrpBoost = m.getInt("lteRsrpBoost"); mTdScdmaRscp = m.getInt("TdScdma"); isGsm = m.getBoolean("isGsm"); + mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel"); } /** @@ -1110,6 +1150,7 @@ public class SignalStrength implements Parcelable { m.putInt("lteRsrpBoost", mLteRsrpBoost); m.putInt("TdScdma", mTdScdmaRscp); m.putBoolean("isGsm", isGsm); + m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel); } /** diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 6029995f2468..31ee3157d225 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -387,6 +387,112 @@ public final class SmsManager { } /** + * Send a text based SMS with messaging options. + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param text the body of the message to send + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully sent, or failed. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> + * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> + * <code>RESULT_ERROR_RADIO_OFF</code><br> + * <code>RESULT_ERROR_NULL_PDU</code><br> + * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include + * the extra "errorCode" containing a radio technology specific value, + * generally only useful for troubleshooting.<br> + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + * @param priority Priority level of the message + * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 + * --------------------------------- + * PRIORITY | Level of Priority + * --------------------------------- + * '00' | Normal + * '01' | Interactive + * '10' | Urgent + * '11' | Emergency + * ---------------------------------- + * Any Other values included Negative considered as Invalid Priority Indicator of the message. + * @param expectMore is a boolean to indicate the sending messages through same link or not. + * @param validityPeriod Validity Period of the message in mins. + * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. + * Validity Period(Minimum) -> 5 mins + * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). + * Any Other values included Negative considered as Invalid Validity Period of the message. + * + * @throws IllegalArgumentException if destinationAddress or text are empty + * {@hide} + */ + public void sendTextMessage( + String destinationAddress, String scAddress, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent, + int priority, boolean expectMore, int validityPeriod) { + sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent, + true /* persistMessage*/, priority, expectMore, validityPeriod); + } + + private void sendTextMessageInternal( + String destinationAddress, String scAddress, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage, + int priority, boolean expectMore, int validityPeriod) { + if (TextUtils.isEmpty(destinationAddress)) { + throw new IllegalArgumentException("Invalid destinationAddress"); + } + + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Invalid message body"); + } + + if (priority < 0x00 || priority > 0x03) { + throw new IllegalArgumentException("Invalid priority"); + } + + if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) { + throw new IllegalArgumentException("Invalid validity period"); + } + + try { + ISms iccISms = getISmsServiceOrThrow(); + if (iccISms != null) { + iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(), + ActivityThread.currentPackageName(), destinationAddress, scAddress, text, + sentIntent, deliveryIntent, persistMessage, priority, expectMore, + validityPeriod); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Send a text based SMS without writing it into the SMS Provider. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier + * privileges. + * </p> + * + * @see #sendTextMessage(String, String, String, PendingIntent, + * PendingIntent, int, boolean, int) + * @hide + */ + public void sendTextMessageWithoutPersisting( + String destinationAddress, String scAddress, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent, int priority, + boolean expectMore, int validityPeriod) { + sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent, + false /* persistMessage */, priority, expectMore, validityPeriod); + } + + /** + * * Inject an SMS PDU into the android application framework. * * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier @@ -541,6 +647,140 @@ public final class SmsManager { } /** + * Send a multi-part text based SMS with messaging options. The callee should have already + * divided the message into correctly sized parts by calling + * <code>divideMessage</code>. + * + * <p class="note"><strong>Note:</strong> Using this method requires that your app has the + * {@link android.Manifest.permission#SEND_SMS} permission.</p> + * + * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if + * <em>and only if</em> an app is not selected as the default SMS app, the system automatically + * writes messages sent using this method to the SMS Provider (the default SMS app is always + * responsible for writing its sent messages to the SMS Provider). For information about + * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p> + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param parts an <code>ArrayList</code> of strings that, in order, + * comprise the original message + * @param sentIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been sent. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> + * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> + * <code>RESULT_ERROR_RADIO_OFF</code><br> + * <code>RESULT_ERROR_NULL_PDU</code><br> + * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include + * the extra "errorCode" containing a radio technology specific value, + * generally only useful for troubleshooting.<br> + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been delivered + * to the recipient. The raw pdu of the status report is in the + * extended data ("pdu"). + * @param priority Priority level of the message + * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 + * --------------------------------- + * PRIORITY | Level of Priority + * --------------------------------- + * '00' | Normal + * '01' | Interactive + * '10' | Urgent + * '11' | Emergency + * ---------------------------------- + * Any Other values included Negative considered as Invalid Priority Indicator of the message. + * @param expectMore is a boolean to indicate the sending messages through same link or not. + * @param validityPeriod Validity Period of the message in mins. + * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. + * Validity Period(Minimum) -> 5 mins + * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). + * Any Other values included Negative considered as Invalid Validity Period of the message. + * + * @throws IllegalArgumentException if destinationAddress or data are empty + * {@hide} + */ + public void sendMultipartTextMessage( + String destinationAddress, String scAddress, ArrayList<String> parts, + ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents, + int priority, boolean expectMore, int validityPeriod) { + sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents, + deliveryIntents, true /* persistMessage*/); + } + + private void sendMultipartTextMessageInternal( + String destinationAddress, String scAddress, List<String> parts, + List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents, + boolean persistMessage, int priority, boolean expectMore, int validityPeriod) { + if (TextUtils.isEmpty(destinationAddress)) { + throw new IllegalArgumentException("Invalid destinationAddress"); + } + if (parts == null || parts.size() < 1) { + throw new IllegalArgumentException("Invalid message body"); + } + + if (priority < 0x00 || priority > 0x03) { + throw new IllegalArgumentException("Invalid priority"); + } + + if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) { + throw new IllegalArgumentException("Invalid validity period"); + } + + if (parts.size() > 1) { + try { + ISms iccISms = getISmsServiceOrThrow(); + if (iccISms != null) { + iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(), + ActivityThread.currentPackageName(), destinationAddress, scAddress, + parts, sentIntents, deliveryIntents, persistMessage, priority, + expectMore, validityPeriod); + } + } catch (RemoteException ex) { + // ignore it + } + } else { + PendingIntent sentIntent = null; + PendingIntent deliveryIntent = null; + if (sentIntents != null && sentIntents.size() > 0) { + sentIntent = sentIntents.get(0); + } + if (deliveryIntents != null && deliveryIntents.size() > 0) { + deliveryIntent = deliveryIntents.get(0); + } + sendTextMessageInternal(destinationAddress, scAddress, parts.get(0), + sentIntent, deliveryIntent, persistMessage, priority, expectMore, + validityPeriod); + } + } + + /** + * Send a multi-part text based SMS without writing it into the SMS Provider. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier + * privileges. + * </p> + * + * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList, + * ArrayList, int, boolean, int) + * @hide + **/ + public void sendMultipartTextMessageWithoutPersisting( + String destinationAddress, String scAddress, List<String> parts, + List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents, + int priority, boolean expectMore, int validityPeriod) { + sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents, + deliveryIntents, false /* persistMessage*/, priority, expectMore, + validityPeriod); + } + + /** * Send a data based SMS to a specific application port. * * <p class="note"><strong>Note:</strong> Using this method requires that your app has the @@ -1003,7 +1243,7 @@ public final class SmsManager { * <code>getAllMessagesFromIcc</code> * @return <code>ArrayList</code> of <code>SmsMessage</code> objects. */ - private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) { + private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) { ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>(); if (records != null) { int count = records.size(); @@ -1011,7 +1251,8 @@ public final class SmsManager { SmsRawData data = records.get(i); // List contains all records, including "free" records (null) if (data != null) { - SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes()); + SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(), + getSubscriptionId()); if (sms != null) { messages.add(sms); } diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index dcdda8685e02..ec8405035f15 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -267,6 +267,31 @@ public class SmsMessage { } /** + * Create an SmsMessage from an SMS EF record. + * + * @param index Index of SMS record. This should be index in ArrayList + * returned by SmsManager.getAllMessagesFromSim + 1. + * @param data Record data. + * @param subId Subscription Id of the SMS + * @return An SmsMessage representing the record. + * + * @hide + */ + public static SmsMessage createFromEfRecord(int index, byte[] data, int subId) { + SmsMessageBase wrappedMessage; + + if (isCdmaVoice(subId)) { + wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord( + index, data); + } else { + wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord( + index, data); + } + + return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null; + } + + /** * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the * length in bytes (not hex chars) less the SMSC header * @@ -818,6 +843,7 @@ public class SmsMessage { int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId); return (PHONE_TYPE_CDMA == activePhone); } + /** * Decide if the carrier supports long SMS. * {@hide} diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/telephony/Telephony.java index e64e815248e4..1b50861707ed 100644 --- a/telephony/java/android/telephony/Telephony.java +++ b/telephony/java/android/telephony/Telephony.java @@ -3250,4 +3250,69 @@ public final class Telephony { */ public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation"; } + + /** + * Contains carrier identification information. + * @hide + */ + public static final class CarrierIdentification implements BaseColumns { + /** + * Numeric operator ID (as String). {@code MCC + MNC} + * <P>Type: TEXT </P> + */ + public static final String MCCMNC = "mccmnc"; + + /** + * Group id level 1 (as String). + * <P>Type: TEXT </P> + */ + public static final String GID1 = "gid1"; + + /** + * Group id level 2 (as String). + * <P>Type: TEXT </P> + */ + public static final String GID2 = "gid2"; + + /** + * Public Land Mobile Network name. + * <P>Type: TEXT </P> + */ + public static final String PLMN = "plmn"; + + /** + * Prefix xpattern of IMSI (International Mobile Subscriber Identity). + * <P>Type: TEXT </P> + */ + public static final String IMSI_PREFIX_XPATTERN = "imsi_prefix_xpattern"; + + /** + * Service Provider Name. + * <P>Type: TEXT </P> + */ + public static final String SPN = "spn"; + + /** + * Prefer APN name. + * <P>Type: TEXT </P> + */ + public static final String APN = "apn"; + + /** + * User facing carrier name. + * <P>Type: TEXT </P> + */ + public static final String NAME = "carrier_name"; + + /** + * A unique carrier id + * <P>Type: INTEGER </P> + */ + public static final String CID = "carrier_id"; + + /** + * The {@code content://} URI for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://carrier_identification"); + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 450f485317ef..73f59379ff59 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2133,13 +2133,16 @@ public class TelephonyManager { * @hide */ public String getSimOperatorNumeric() { - int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + int subId = mSubId; if (!SubscriptionManager.isUsableSubIdValue(subId)) { - subId = SubscriptionManager.getDefaultSmsSubscriptionId(); + subId = SubscriptionManager.getDefaultDataSubscriptionId(); if (!SubscriptionManager.isUsableSubIdValue(subId)) { - subId = SubscriptionManager.getDefaultVoiceSubscriptionId(); + subId = SubscriptionManager.getDefaultSmsSubscriptionId(); if (!SubscriptionManager.isUsableSubIdValue(subId)) { - subId = SubscriptionManager.getDefaultSubscriptionId(); + subId = SubscriptionManager.getDefaultVoiceSubscriptionId(); + if (!SubscriptionManager.isUsableSubIdValue(subId)) { + subId = SubscriptionManager.getDefaultSubscriptionId(); + } } } } diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java index d8d7f48a56be..b30a3af72c3e 100644 --- a/telephony/java/android/telephony/mbms/FileServiceInfo.java +++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java @@ -17,6 +17,7 @@ package android.telephony.mbms; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -35,6 +36,7 @@ public final class FileServiceInfo extends ServiceInfo implements Parcelable { /** @hide */ @SystemApi + @TestApi public FileServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales, String newServiceId, Date start, Date end, List<FileInfo> newFiles) { diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java index d38d8a712c73..b4ad1d77760a 100644 --- a/telephony/java/android/telephony/mbms/MbmsUtils.java +++ b/telephony/java/android/telephony/mbms/MbmsUtils.java @@ -22,6 +22,8 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.*; import android.content.pm.ServiceInfo; +import android.telephony.MbmsDownloadSession; +import android.telephony.MbmsStreamingSession; import android.util.Log; import java.io.File; @@ -48,24 +50,64 @@ public class MbmsUtils { return new ComponentName(ci.packageName, ci.name); } + private static ComponentName getOverrideServiceName(Context context, String serviceAction) { + String metaDataKey = null; + switch (serviceAction) { + case MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION: + metaDataKey = MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA; + break; + case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION: + metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA; + break; + } + if (metaDataKey == null) { + return null; + } + + ApplicationInfo appInfo; + try { + appInfo = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + if (appInfo.metaData == null) { + return null; + } + String serviceComponent = appInfo.metaData.getString(metaDataKey); + if (serviceComponent == null) { + return null; + } + return ComponentName.unflattenFromString(serviceComponent); + } + public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) { // Query for the proper service PackageManager packageManager = context.getPackageManager(); Intent queryIntent = new Intent(); queryIntent.setAction(serviceAction); - List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent, - PackageManager.MATCH_SYSTEM_ONLY); - if (downloadServices == null || downloadServices.size() == 0) { - Log.w(LOG_TAG, "No download services found, cannot get service info"); + ComponentName overrideService = getOverrideServiceName(context, serviceAction); + List<ResolveInfo> services; + if (overrideService == null) { + services = packageManager.queryIntentServices(queryIntent, + PackageManager.MATCH_SYSTEM_ONLY); + } else { + queryIntent.setComponent(overrideService); + services = packageManager.queryIntentServices(queryIntent, + PackageManager.MATCH_ALL); + } + + if (services == null || services.size() == 0) { + Log.w(LOG_TAG, "No MBMS services found, cannot get service info"); return null; } - if (downloadServices.size() > 1) { - Log.w(LOG_TAG, "More than one download service found, cannot get unique service"); + if (services.size() > 1) { + Log.w(LOG_TAG, "More than one MBMS service found, cannot get unique service"); return null; } - return downloadServices.get(0).serviceInfo; + return services.get(0).serviceInfo; } public static int startBinding(Context context, String serviceAction, diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java index c704f346e311..ef2a14aa26b3 100644 --- a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java +++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java @@ -17,6 +17,7 @@ package android.telephony.mbms; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -41,6 +42,7 @@ public final class StreamingServiceInfo extends ServiceInfo implements Parcelabl * @hide */ @SystemApi + @TestApi public StreamingServiceInfo(Map<Locale, String> names, String className, List<Locale> locales, String serviceId, Date start, Date end) { super(names, className, locales, serviceId, start, end); diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java index 9ccdd56fd82d..4fee3df83c9a 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -18,6 +18,7 @@ package android.telephony.mbms.vendor; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.Intent; import android.os.Binder; import android.os.IBinder; @@ -42,6 +43,7 @@ import java.util.Map; * @hide */ @SystemApi +@TestApi public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>(); private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>(); diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java index a2381536ac0c..db177c0c7768 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java @@ -18,6 +18,7 @@ package android.telephony.mbms.vendor; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.Intent; import android.net.Uri; import android.os.Binder; @@ -38,6 +39,7 @@ import java.util.List; * @hide */ @SystemApi +@TestApi public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { /** * Initialize streaming service for this app and subId, registering the listener. diff --git a/telephony/java/com/android/ims/ImsConferenceState.java b/telephony/java/com/android/ims/ImsConferenceState.java index c57ef98abb4d..0afde88b8918 100644 --- a/telephony/java/com/android/ims/ImsConferenceState.java +++ b/telephony/java/com/android/ims/ImsConferenceState.java @@ -79,6 +79,8 @@ public class ImsConferenceState implements Parcelable { public static final String STATUS_DISCONNECTED = "disconnected"; public static final String STATUS_MUTED_VIA_FOCUS = "muted-via-focus"; public static final String STATUS_CONNECT_FAIL = "connect-fail"; + public static final String STATUS_SEND_ONLY = "sendonly"; + public static final String STATUS_SEND_RECV = "sendrecv"; /** * conference-info : SIP status code (integer) @@ -156,15 +158,53 @@ public class ImsConferenceState implements Parcelable { } else if (status.equals(STATUS_ALERTING) || status.equals(STATUS_DIALING_OUT)) { return Connection.STATE_DIALING; - } else if (status.equals(STATUS_ON_HOLD)) { + } else if (status.equals(STATUS_ON_HOLD) || + status.equals(STATUS_SEND_ONLY)) { return Connection.STATE_HOLDING; } else if (status.equals(STATUS_CONNECTED) || status.equals(STATUS_MUTED_VIA_FOCUS) || - status.equals(STATUS_DISCONNECTING)) { + status.equals(STATUS_DISCONNECTING) || + status.equals(STATUS_SEND_RECV)) { return Connection.STATE_ACTIVE; } else if (status.equals(STATUS_DISCONNECTED)) { return Connection.STATE_DISCONNECTED; } return Call.STATE_ACTIVE; } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(ImsConferenceState.class.getSimpleName()); + sb.append(" "); + if (mParticipants.size() > 0) { + Set<Entry<String, Bundle>> entries = mParticipants.entrySet(); + + if (entries != null) { + Iterator<Entry<String, Bundle>> iterator = entries.iterator(); + sb.append("<"); + while (iterator.hasNext()) { + Entry<String, Bundle> entry = iterator.next(); + sb.append(entry.getKey()); + sb.append(": "); + Bundle participantData = entry.getValue(); + + for (String key : participantData.keySet()) { + sb.append(key); + sb.append("="); + if (ENDPOINT.equals(key) || USER.equals(key)) { + sb.append(android.telecom.Log.pii(participantData.get(key))); + } else { + sb.append(participantData.get(key)); + } + sb.append(", "); + } + } + sb.append(">"); + } + } + sb.append("]"); + return sb.toString(); + } } diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl index fe37531c0611..a4eb424ab66e 100644 --- a/telephony/java/com/android/internal/telephony/ISms.aidl +++ b/telephony/java/com/android/internal/telephony/ISms.aidl @@ -187,6 +187,57 @@ interface ISms { in PendingIntent deliveryIntent, in boolean persistMessage); /** + * Send an SMS with options using Subscription Id. + * + * @param subId the subId on which the SMS has to be sent. + * @param destAddr the address to send the message to + * @param scAddr the SMSC to send the message through, or NULL for the + * default SMSC + * @param text the body of the message to send + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is sucessfully sent, or failed. + * The result code will be <code>Activity.RESULT_OK<code> for success, + * or one of these errors:<br> + * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> + * <code>RESULT_ERROR_RADIO_OFF</code><br> + * <code>RESULT_ERROR_NULL_PDU</code><br> + * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include + * the extra "errorCode" containing a radio technology specific value, + * generally only useful for troubleshooting.<br> + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + * @param persistMessageForNonDefaultSmsApp whether the sent message should + * be automatically persisted in the SMS db. It only affects messages sent + * by a non-default SMS app. Currently only the carrier app can set this + * parameter to false to skip auto message persistence. + * @param priority Priority level of the message + * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 + * --------------------------------- + * PRIORITY | Level of Priority + * --------------------------------- + * '00' | Normal + * '01' | Interactive + * '10' | Urgent + * '11' | Emergency + * ---------------------------------- + * Any Other values included Negative considered as Invalid Priority Indicator of the message. + * @param expectMore is a boolean to indicate the sending message is multi segmented or not. + * @param validityPeriod Validity Period of the message in mins. + * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. + * Validity Period(Minimum) -> 5 mins + * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). + * Any Other values included Negative considered as Invalid Validity Period of the message. + */ + void sendTextForSubscriberWithOptions(in int subId, String callingPkg, in String destAddr, + in String scAddr, in String text, in PendingIntent sentIntent, + in PendingIntent deliveryIntent, in boolean persistMessageForNonDefaultSmsApp, + in int priority, in boolean expectMore, in int validityPeriod); + + /** * Inject an SMS PDU into the android platform. * * @param subId the subId on which the SMS has to be injected. @@ -234,6 +285,56 @@ interface ISms { in List<PendingIntent> deliveryIntents, in boolean persistMessageForNonDefaultSmsApp); /** + * Send a multi-part text based SMS with options using Subscription Id. + * + * @param subId the subId on which the SMS has to be sent. + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param parts an <code>ArrayList</code> of strings that, in order, + * comprise the original message + * @param sentIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been sent. + * The result code will be <code>Activity.RESULT_OK<code> for success, + * or one of these errors: + * <code>RESULT_ERROR_GENERIC_FAILURE</code> + * <code>RESULT_ERROR_RADIO_OFF</code> + * <code>RESULT_ERROR_NULL_PDU</code>. + * @param deliveryIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been delivered + * to the recipient. The raw pdu of the status report is in the + * extended data ("pdu"). + * @param persistMessageForNonDefaultSmsApp whether the sent message should + * be automatically persisted in the SMS db. It only affects messages sent + * by a non-default SMS app. Currently only the carrier app can set this + * parameter to false to skip auto message persistence. + * @param priority Priority level of the message + * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 + * --------------------------------- + * PRIORITY | Level of Priority + * --------------------------------- + * '00' | Normal + * '01' | Interactive + * '10' | Urgent + * '11' | Emergency + * ---------------------------------- + * Any Other values included Negative considered as Invalid Priority Indicator of the message. + * @param expectMore is a boolean to indicate the sending message is multi segmented or not. + * @param validityPeriod Validity Period of the message in mins. + * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. + * Validity Period(Minimum) -> 5 mins + * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). + * Any Other values included Negative considered as Invalid Validity Period of the message. + */ + void sendMultipartTextForSubscriberWithOptions(in int subId, String callingPkg, + in String destinationAddress, in String scAddress, in List<String> parts, + in List<PendingIntent> sentIntents, in List<PendingIntent> deliveryIntents, + in boolean persistMessageForNonDefaultSmsApp, in int priority, in boolean expectMore, + in int validityPeriod); + + /** * Enable reception of cell broadcast (SMS-CB) messages with the given * message identifier and RAN type. The RAN type specify this message ID * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 7a53ef63e2a8..14c5f4bebebb 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -99,6 +99,15 @@ public class SmsMessage extends SmsMessageBase { private static final int RETURN_NO_ACK = 0; private static final int RETURN_ACK = 1; + /** + * Supported priority modes for CDMA SMS messages + * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1) + */ + private static final int PRIORITY_NORMAL = 0x0; + private static final int PRIORITY_INTERACTIVE = 0x1; + private static final int PRIORITY_URGENT = 0x2; + private static final int PRIORITY_EMERGENCY = 0x3; + private SmsEnvelope mEnvelope; private BearerData mBearerData; @@ -211,6 +220,26 @@ public class SmsMessage extends SmsMessageBase { */ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader) { + return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1); + } + + /** + * Get an SMS-SUBMIT PDU for a destination address and a message + * + * @param scAddr Service Centre address. Null means use default. + * @param destAddr Address of the recipient. + * @param message String representation of the message payload. + * @param statusReportRequested Indicates whether a report is requested for this message. + * @param smsHeader Array containing the data for the User Data Header, preceded + * by the Element Identifiers. + * @param priority Priority level of the message + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + * @hide + */ + public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message, + boolean statusReportRequested, SmsHeader smsHeader, int priority) { /** * TODO(cleanup): Do we really want silent failure like this? @@ -224,7 +253,7 @@ public class SmsMessage extends SmsMessageBase { UserData uData = new UserData(); uData.payloadStr = message; uData.userDataHeader = smsHeader; - return privateGetSubmitPdu(destAddr, statusReportRequested, uData); + return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority); } /** @@ -282,6 +311,22 @@ public class SmsMessage extends SmsMessageBase { } /** + * Get an SMS-SUBMIT PDU for a data message to a destination address & port + * + * @param destAddr the address of the destination for the message + * @param userData the data for the message + * @param statusReportRequested Indicates whether a report is requested for this message. + * @param priority Priority level of the message + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + */ + public static SubmitPdu getSubmitPdu(String destAddr, UserData userData, + boolean statusReportRequested, int priority) { + return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority); + } + + /** * Note: This function is a GSM specific functionality which is not supported in CDMA mode. */ @Override @@ -764,6 +809,15 @@ public class SmsMessage extends SmsMessageBase { */ private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested, UserData userData) { + return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1); + } + + /** + * Creates BearerData and Envelope from parameters for a Submit SMS. + * @return byte stream for SubmitPdu. + */ + private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested, + UserData userData, int priority) { /** * TODO(cleanup): give this function a more meaningful name. @@ -792,6 +846,10 @@ public class SmsMessage extends SmsMessageBase { bearerData.userAckReq = false; bearerData.readAckReq = false; bearerData.reportReq = false; + if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) { + bearerData.priorityIndicatorSet = true; + bearerData.priority = priority; + } bearerData.userData = userData; diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 1ca19e01d6c8..4f5bfa919135 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -89,6 +89,18 @@ public class SmsMessage extends SmsMessageBase { private int mVoiceMailCount = 0; + private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00; + private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01; + private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02; + private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03; + + //Validity Period min - 5 mins + private static final int VALIDITY_PERIOD_MIN = 5; + //Validity Period max - 63 weeks + private static final int VALIDITY_PERIOD_MAX = 635040; + + private static final int INVALID_VALIDITY_PERIOD = -1; + public static class SubmitPdu extends SubmitPduBase { } @@ -202,6 +214,45 @@ public class SmsMessage extends SmsMessageBase { } /** + * Get Encoded Relative Validty Period Value from Validity period in mins. + * + * @param validityPeriod Validity period in mins. + * + * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. + * ||relValidityPeriod (TP-VP) || || validityPeriod || + * + * 0 to 143 ---> (TP-VP + 1) x 5 minutes + * + * 144 to 167 ---> 12 hours + ((TP-VP -143) x 30 minutes) + * + * 168 to 196 ---> (TP-VP - 166) x 1 day + * + * 197 to 255 ---> (TP-VP - 192) x 1 week + * + * @return relValidityPeriod Encoded Relative Validity Period Value. + * @hide + */ + public static int getRelativeValidityPeriod(int validityPeriod) { + int relValidityPeriod = INVALID_VALIDITY_PERIOD; + + if (validityPeriod < VALIDITY_PERIOD_MIN || validityPeriod > VALIDITY_PERIOD_MAX) { + Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod); + return relValidityPeriod; + } + + if (validityPeriod <= 720) { + relValidityPeriod = (validityPeriod / 5) - 1; + } else if (validityPeriod <= 1440) { + relValidityPeriod = ((validityPeriod - 720) / 30) + 143; + } else if (validityPeriod <= 43200) { + relValidityPeriod = (validityPeriod / 1440) + 166; + } else if (validityPeriod <= 635040) { + relValidityPeriod = (validityPeriod / 10080) + 192; + } + return relValidityPeriod; + } + + /** * Get an SMS-SUBMIT PDU for a destination address and a message * * @param scAddress Service Centre address. Null means use default. @@ -236,6 +287,29 @@ public class SmsMessage extends SmsMessageBase { String destinationAddress, String message, boolean statusReportRequested, byte[] header, int encoding, int languageTable, int languageShiftTable) { + return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, + header, encoding, languageTable, languageShiftTable, -1); + } + + /** + * Get an SMS-SUBMIT PDU for a destination address and a message using the + * specified encoding. + * + * @param scAddress Service Centre address. Null means use default. + * @param encoding Encoding defined by constants in + * com.android.internal.telephony.SmsConstants.ENCODING_* + * @param languageTable + * @param languageShiftTable + * @param validityPeriod Validity Period of the message in Minutes. + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + * @hide + */ + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, String message, + boolean statusReportRequested, byte[] header, int encoding, + int languageTable, int languageShiftTable, int validityPeriod) { // Perform null parameter checks. if (message == null || destinationAddress == null) { @@ -272,8 +346,19 @@ public class SmsMessage extends SmsMessageBase { } SubmitPdu ret = new SubmitPdu(); - // MTI = SMS-SUBMIT, UDHI = header != null - byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00)); + + int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE; + int relativeValidityPeriod = INVALID_VALIDITY_PERIOD; + + // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3 + //bit 4:3 = 10 - TP-VP field present - relative format + if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) { + validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE; + } + + byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) | + (header != null ? 0x40 : 0x00)); + ByteArrayOutputStream bo = getSubmitPduHead( scAddress, destinationAddress, mtiByte, statusReportRequested, ret); @@ -338,7 +423,11 @@ public class SmsMessage extends SmsMessageBase { bo.write(0x08); } - // (no TP-Validity-Period) + if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) { + // ( TP-Validity-Period - relative format) + bo.write(relativeValidityPeriod); + } + bo.write(userData, 0, userData.length); ret.encodedMessage = bo.toByteArray(); return ret; @@ -388,6 +477,24 @@ public class SmsMessage extends SmsMessageBase { } /** + * Get an SMS-SUBMIT PDU for a destination address and a message + * + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message + * @param statusReportRequested staus report of the message Requested + * @param validityPeriod Validity Period of the message in Minutes. + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + */ + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, String message, + boolean statusReportRequested, int validityPeriod) { + return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, + null, ENCODING_UNKNOWN, 0, 0, validityPeriod); + } + + /** * Get an SMS-SUBMIT PDU for a data message to a destination address & port * * @param scAddress Service Centre address. null == use default diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index 62d570c80043..99a82ad00d25 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -567,4 +567,12 @@ public class IccUtils { } while (valueIndex < endIndex); return result; } + + public static String getDecimalSubstring(String iccId) { + int position; + for (position = 0; position < iccId.length(); position ++) { + if (!Character.isDigit(iccId.charAt(position))) break; + } + return iccId.substring( 0, position ); + } } diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java index 7346f9f95041..cd2d0985c793 100644 --- a/tests/net/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java @@ -16,6 +16,7 @@ package android.net; +import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; @@ -26,13 +27,12 @@ import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; - -import android.net.NetworkCapabilities; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; @@ -159,4 +159,25 @@ public class NetworkCapabilitiesTest { assertNotEquals("", nc1.describeImmutableDifferences(nc2)); assertEquals("", nc1.describeImmutableDifferences(nc1)); } + + @Test + public void testLinkBandwidthUtils() { + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, NetworkCapabilities + .minBandwidth(LINK_BANDWIDTH_UNSPECIFIED, LINK_BANDWIDTH_UNSPECIFIED)); + assertEquals(10, NetworkCapabilities + .minBandwidth(LINK_BANDWIDTH_UNSPECIFIED, 10)); + assertEquals(10, NetworkCapabilities + .minBandwidth(10, LINK_BANDWIDTH_UNSPECIFIED)); + assertEquals(10, NetworkCapabilities + .minBandwidth(10, 20)); + + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, NetworkCapabilities + .maxBandwidth(LINK_BANDWIDTH_UNSPECIFIED, LINK_BANDWIDTH_UNSPECIFIED)); + assertEquals(10, NetworkCapabilities + .maxBandwidth(LINK_BANDWIDTH_UNSPECIFIED, 10)); + assertEquals(10, NetworkCapabilities + .maxBandwidth(10, LINK_BANDWIDTH_UNSPECIFIED)); + assertEquals(20, NetworkCapabilities + .maxBandwidth(10, 20)); + } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 27a29b68e12d..113cd37f1a27 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -23,21 +23,44 @@ import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.ConnectivityManager.TYPE_WIFI; -import static android.net.ConnectivityManager.getNetworkTypeName; -import static android.net.NetworkCapabilities.*; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; +import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; +import static android.net.NetworkCapabilities.NET_CAPABILITY_IA; +import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; +import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; + +import static com.android.internal.util.TestUtils.waitForIdleHandler; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static com.android.internal.util.TestUtils.waitForIdleHandler; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.NotificationManager; @@ -45,7 +68,6 @@ import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; -import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; @@ -64,7 +86,6 @@ import android.net.MatchAllNetworkSpecifier; import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; -import android.net.NetworkConfig; import android.net.NetworkFactory; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; @@ -79,13 +100,9 @@ import android.net.util.MultinetworkPolicyTracker; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; -import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; -import android.os.MessageQueue; -import android.os.Messenger; -import android.os.MessageQueue.IdleHandler; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; @@ -96,10 +113,8 @@ import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.test.mock.MockContentResolver; -import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; -import android.util.LogPrinter; import com.android.internal.util.WakeupMessage; import com.android.internal.util.test.BroadcastInterceptingContext; @@ -109,7 +124,6 @@ import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; -import com.android.server.connectivity.NetworkMonitor.CaptivePortalProbeResult; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; @@ -160,6 +174,7 @@ public class ConnectivityServiceTest { @Mock IpConnectivityMetrics.Logger mMetricsService; @Mock DefaultNetworkMetrics mDefaultNetworkMetrics; + @Mock INetworkStatsService mStatsService; // This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods // do not go through ConnectivityService but talk to netd directly, so they don't automatically @@ -858,7 +873,7 @@ public class ConnectivityServiceTest { NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class)); mService = new WrappedConnectivityService(mServiceContext, mock(INetworkManagementService.class), - mock(INetworkStatsService.class), + mStatsService, mock(INetworkPolicyManager.class), mock(IpConnectivityLog.class)); @@ -3440,6 +3455,40 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(networkCallback); } + @Test + public void testStatsIfacesChanged() throws Exception { + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + + // Simple connection should have updated ifaces + mCellNetworkAgent.connect(false); + waitForIdle(); + verify(mStatsService, atLeastOnce()).forceUpdateIfaces(); + reset(mStatsService); + + // Metered change should update ifaces + mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + waitForIdle(); + verify(mStatsService, atLeastOnce()).forceUpdateIfaces(); + reset(mStatsService); + + mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + waitForIdle(); + verify(mStatsService, atLeastOnce()).forceUpdateIfaces(); + reset(mStatsService); + + // Captive portal change shouldn't update ifaces + mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); + waitForIdle(); + verify(mStatsService, never()).forceUpdateIfaces(); + reset(mStatsService); + + // Roaming change should update ifaces + mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); + waitForIdle(); + verify(mStatsService, atLeastOnce()).forceUpdateIfaces(); + reset(mStatsService); + } + private void checkDirectlyConnectedRoutes(Object callbackObj, Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) { assertTrue(callbackObj instanceof LinkProperties); diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java index b4b809480ffb..9e97d84ba680 100644 --- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java +++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java @@ -206,7 +206,7 @@ public class IpSecServiceParameterizedTest { eq(CRYPT_KEY), anyInt(), eq(""), - isNull(), + eq(new byte[] {}), eq(0), anyInt(), anyInt(), @@ -227,7 +227,7 @@ public class IpSecServiceParameterizedTest { eq(CRYPT_KEY), anyInt(), eq(""), - isNull(), + eq(new byte[] {}), eq(0), anyInt(), anyInt(), @@ -256,10 +256,10 @@ public class IpSecServiceParameterizedTest { anyLong(), eq(TEST_SPI_OUT), eq(""), - isNull(), + eq(new byte[] {}), eq(0), eq(""), - isNull(), + eq(new byte[] {}), eq(0), eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM), eq(CRYPT_KEY), @@ -277,10 +277,10 @@ public class IpSecServiceParameterizedTest { anyLong(), eq(TEST_SPI_IN), eq(""), - isNull(), + eq(new byte[] {}), eq(0), eq(""), - isNull(), + eq(new byte[] {}), eq(0), eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM), eq(CRYPT_KEY), diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index ad6ebf933776..a65bb2436287 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -198,37 +198,33 @@ public class IpConnectivityEventBuilderTest { @Test public void testDefaultNetworkEventSerialization() { - DefaultNetworkEvent ev = new DefaultNetworkEvent(); + DefaultNetworkEvent ev = new DefaultNetworkEvent(1001); ev.netId = 102; - ev.prevNetId = 101; - ev.transportTypes = new int[]{1, 2, 3}; - ev.prevIPv4 = true; - ev.prevIPv6 = true; + ev.transports = 2; + ev.previousTransports = 4; + ev.ipv4 = true; + ev.initialScore = 20; + ev.finalScore = 60; + ev.durationMs = 54; + ev.validatedMs = 27; String want = String.join("\n", "dropped_events: 0", "events <", " if_name: \"\"", - " link_layer: 0", + " link_layer: 4", " network_id: 102", " time_ms: 0", - " transports: 0", + " transports: 2", " default_network_event <", - " default_network_duration_ms: 0", - " final_score: 0", - " initial_score: 0", - " ip_support: 0", - " network_id <", - " network_id: 102", - " >", + " default_network_duration_ms: 54", + " final_score: 60", + " initial_score: 20", + " ip_support: 1", " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 101", - " >", - " previous_network_ip_support: 3", - " transport_types: 1", - " transport_types: 2", - " transport_types: 3", + " previous_default_network_link_layer: 1", + " previous_network_ip_support: 0", + " validation_duration_ms: 27", " >", ">", "version: 2\n"); diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index 6c1decc3b3b8..b48ff8da2ba4 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -188,119 +188,99 @@ public class IpConnectivityMetricsTest { {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)}, }; + long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs; + long durationMs = 1001; for (NetworkAgentInfo[] pair : defaultNetworks) { - mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(pair[1], pair[0]); + timeMs += durationMs; + durationMs += durationMs; + mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, pair[1], pair[0]); } String want = String.join("\n", "dropped_events: 0", "events <", " if_name: \"\"", - " link_layer: 0", - " network_id: 100", + " link_layer: 5", + " network_id: 0", " time_ms: 0", " transports: 0", " default_network_event <", - " default_network_duration_ms: 0", + " default_network_duration_ms: 1001", " final_score: 0", " initial_score: 0", " ip_support: 0", - " network_id <", - " network_id: 100", - " >", " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 0", - " >", + " previous_default_network_link_layer: 0", " previous_network_ip_support: 0", - " transport_types: 0", + " validation_duration_ms: 0", " >", ">", "events <", " if_name: \"\"", - " link_layer: 0", - " network_id: 101", + " link_layer: 2", + " network_id: 100", " time_ms: 0", - " transports: 0", + " transports: 1", " default_network_event <", - " default_network_duration_ms: 0", - " final_score: 0", - " initial_score: 0", - " ip_support: 0", - " network_id <", - " network_id: 101", - " >", + " default_network_duration_ms: 2002", + " final_score: 50", + " initial_score: 10", + " ip_support: 3", " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 100", - " >", - " previous_network_ip_support: 3", - " transport_types: 1", + " previous_default_network_link_layer: 0", + " previous_network_ip_support: 0", + " validation_duration_ms: 2002", " >", ">", "events <", " if_name: \"\"", - " link_layer: 0", - " network_id: 0", + " link_layer: 4", + " network_id: 101", " time_ms: 0", - " transports: 0", + " transports: 2", " default_network_event <", - " default_network_duration_ms: 0", - " final_score: 0", - " initial_score: 0", - " ip_support: 0", - " network_id <", - " network_id: 0", - " >", + " default_network_duration_ms: 4004", + " final_score: 60", + " initial_score: 20", + " ip_support: 1", " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 101", - " >", - " previous_network_ip_support: 1", + " previous_default_network_link_layer: 2", + " previous_network_ip_support: 0", + " validation_duration_ms: 4004", " >", ">", "events <", " if_name: \"\"", - " link_layer: 0", - " network_id: 102", + " link_layer: 5", + " network_id: 0", " time_ms: 0", " transports: 0", " default_network_event <", - " default_network_duration_ms: 0", + " default_network_duration_ms: 8008", " final_score: 0", " initial_score: 0", " ip_support: 0", - " network_id <", - " network_id: 102", - " >", " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 0", - " >", + " previous_default_network_link_layer: 4", " previous_network_ip_support: 0", - " transport_types: 0", + " validation_duration_ms: 0", " >", ">", "events <", " if_name: \"\"", - " link_layer: 0", - " network_id: 103", + " link_layer: 2", + " network_id: 102", " time_ms: 0", - " transports: 0", + " transports: 1", " default_network_event <", - " default_network_duration_ms: 0", - " final_score: 0", - " initial_score: 0", - " ip_support: 0", - " network_id <", - " network_id: 103", - " >", + " default_network_duration_ms: 16016", + " final_score: 50", + " initial_score: 10", + " ip_support: 3", " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 102", - " >", - " previous_network_ip_support: 3", - " transport_types: 1", + " previous_default_network_link_layer: 4", + " previous_network_ip_support: 0", + " validation_duration_ms: 16016", " >", ">", "version: 2\n"); @@ -379,12 +359,13 @@ public class IpConnectivityMetricsTest { wakeupEvent("wlan0", 10008); wakeupEvent("rmnet0", 1000); + long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs; final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI}); NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell); NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi); - mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(cellNai, null); - mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(wifiNai, cellNai); + mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 200, cellNai, null); + mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 300, wifiNai, cellNai); String want = String.join("\n", "dropped_events: 0", @@ -473,46 +454,36 @@ public class IpConnectivityMetricsTest { ">", "events <", " if_name: \"\"", - " link_layer: 0", - " network_id: 100", + " link_layer: 5", + " network_id: 0", " time_ms: 0", " transports: 0", " default_network_event <", - " default_network_duration_ms: 0", + " default_network_duration_ms: 200", " final_score: 0", " initial_score: 0", " ip_support: 0", - " network_id <", - " network_id: 100", - " >", " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 0", - " >", + " previous_default_network_link_layer: 0", " previous_network_ip_support: 0", - " transport_types: 0", + " validation_duration_ms: 0", " >", ">", "events <", " if_name: \"\"", - " link_layer: 0", - " network_id: 101", + " link_layer: 2", + " network_id: 100", " time_ms: 0", - " transports: 0", + " transports: 1", " default_network_event <", - " default_network_duration_ms: 0", - " final_score: 0", - " initial_score: 0", - " ip_support: 0", - " network_id <", - " network_id: 101", - " >", + " default_network_duration_ms: 100", + " final_score: 50", + " initial_score: 50", + " ip_support: 2", " no_default_network_duration_ms: 0", - " previous_network_id <", - " network_id: 100", - " >", - " previous_network_ip_support: 2", - " transport_types: 1", + " previous_default_network_link_layer: 0", + " previous_network_ip_support: 0", + " validation_duration_ms: 100", " >", ">", "events <", diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index 6723601fc5c3..83194d9e6fa9 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -82,9 +82,8 @@ public class NetdEventListenerServiceTest { public void testWakeupEventLogging() throws Exception { final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH; - // Assert no events - String[] events1 = listNetdEvent(); - assertEquals(new String[]{""}, events1); + // Baseline without any event + String[] baseline = listNetdEvent(); long now = System.currentTimeMillis(); String prefix = "iface:wlan0"; @@ -93,7 +92,7 @@ public class NetdEventListenerServiceTest { mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now); } - String[] events2 = listNetdEvent(); + String[] events2 = remove(listNetdEvent(), baseline); int expectedLength2 = uids.length + 1; // +1 for the WakeupStats line assertEquals(expectedLength2, events2.length); assertContains(events2[0], "WakeupStats"); @@ -111,7 +110,7 @@ public class NetdEventListenerServiceTest { mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, ts); } - String[] events3 = listNetdEvent(); + String[] events3 = remove(listNetdEvent(), baseline); int expectedLength3 = BUFFER_LENGTH + 1; // +1 for the WakeupStats line assertEquals(expectedLength3, events3.length); assertContains(events2[0], "WakeupStats"); @@ -126,7 +125,7 @@ public class NetdEventListenerServiceTest { uid = 45678; mNetdEventListenerService.onWakeupEvent(prefix, uid, uid, now); - String[] events4 = listNetdEvent(); + String[] events4 = remove(listNetdEvent(), baseline); String lastEvent = events4[events4.length - 1]; assertContains(lastEvent, "WakeupEvent"); assertContains(lastEvent, "wlan0"); @@ -423,7 +422,7 @@ public class NetdEventListenerServiceTest { final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); new Thread(() -> { while (System.currentTimeMillis() < stop) { - mNetdEventListenerService.dump(pw); + mNetdEventListenerService.list(pw); } }).start(); } @@ -461,4 +460,16 @@ public class NetdEventListenerServiceTest { static void assertContains(String got, String want) { assertTrue(got + " did not contain \"" + want + "\"", got.contains(want)); } + + static <T> T[] remove(T[] array, T[] filtered) { + List<T> c = Arrays.asList(filtered); + int next = 0; + for (int i = 0; i < array.length; i++) { + if (c.contains(array[i])) { + continue; + } + array[next++] = array[i]; + } + return Arrays.copyOf(array, next); + } } diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index fe396c3a5c40..c29363cd1a4f 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -20,11 +20,30 @@ import static android.content.pm.UserInfo.FLAG_ADMIN; import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE; import static android.content.pm.UserInfo.FLAG_PRIMARY; import static android.content.pm.UserInfo.FLAG_RESTRICTED; +import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.AdditionalMatchers.*; -import static org.mockito.Mockito.*; +import static org.mockito.AdditionalMatchers.aryEq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.annotation.UserIdInt; import android.app.AppOpsManager; @@ -36,6 +55,9 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkInfo.DetailedState; import android.net.UidRange; import android.net.VpnService; @@ -45,17 +67,17 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; -import android.support.test.runner.AndroidJUnit4; import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.R; import com.android.internal.net.VpnConfig; -import org.junit.runner.RunWith; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.InOrder; import org.mockito.Mock; @@ -64,6 +86,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -114,6 +137,7 @@ public class VpnTest { @Mock private AppOpsManager mAppOps; @Mock private NotificationManager mNotificationManager; @Mock private Vpn.SystemServices mSystemServices; + @Mock private ConnectivityManager mConnectivityManager; @Before public void setUp() throws Exception { @@ -127,6 +151,8 @@ public class VpnTest { when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE))) .thenReturn(mNotificationManager); + when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))) + .thenReturn(mConnectivityManager); when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)) .thenReturn(Resources.getSystem().getString( R.string.config_customVpnAlwaysOnDisconnectedDialogComponent)); @@ -397,6 +423,66 @@ public class VpnTest { order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle)); } + @Test + public void testCapabilities() { + final Vpn vpn = createVpn(primaryUser.id); + setMockedUsers(primaryUser); + + final Network mobile = new Network(1); + final Network wifi = new Network(2); + + final Map<Network, NetworkCapabilities> networks = new HashMap<>(); + networks.put(mobile, new NetworkCapabilities() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_METERED) + .setLinkDownstreamBandwidthKbps(10)); + networks.put(wifi, new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NET_CAPABILITY_INTERNET) + .addCapability(NET_CAPABILITY_NOT_ROAMING) + .setLinkUpstreamBandwidthKbps(20)); + setMockedNetworks(networks); + + final NetworkCapabilities caps = new NetworkCapabilities(); + + Vpn.updateCapabilities(mConnectivityManager, new Network[] { }, caps); + assertTrue(caps.hasTransport(TRANSPORT_VPN)); + assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(caps.hasTransport(TRANSPORT_WIFI)); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkDownstreamBandwidthKbps()); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkUpstreamBandwidthKbps()); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + + Vpn.updateCapabilities(mConnectivityManager, new Network[] { mobile }, caps); + assertTrue(caps.hasTransport(TRANSPORT_VPN)); + assertTrue(caps.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(caps.hasTransport(TRANSPORT_WIFI)); + assertEquals(10, caps.getLinkDownstreamBandwidthKbps()); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkUpstreamBandwidthKbps()); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + + Vpn.updateCapabilities(mConnectivityManager, new Network[] { wifi }, caps); + assertTrue(caps.hasTransport(TRANSPORT_VPN)); + assertFalse(caps.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(caps.hasTransport(TRANSPORT_WIFI)); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, caps.getLinkDownstreamBandwidthKbps()); + assertEquals(20, caps.getLinkUpstreamBandwidthKbps()); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + + Vpn.updateCapabilities(mConnectivityManager, new Network[] { mobile, wifi }, caps); + assertTrue(caps.hasTransport(TRANSPORT_VPN)); + assertTrue(caps.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(caps.hasTransport(TRANSPORT_WIFI)); + assertEquals(10, caps.getLinkDownstreamBandwidthKbps()); + assertEquals(20, caps.getLinkUpstreamBandwidthKbps()); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertFalse(caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + } + /** * Mock some methods of vpn object. */ @@ -463,4 +549,11 @@ public class VpnTest { } catch (Exception e) { } } + + private void setMockedNetworks(final Map<Network, NetworkCapabilities> networks) { + doAnswer(invocation -> { + final Network network = (Network) invocation.getArguments()[0]; + return networks.get(network); + }).when(mConnectivityManager).getNetworkCapabilities(any()); + } } diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index fa997958ba6d..375b418bead6 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -1169,9 +1169,8 @@ public class NetworkStatsServiceTest { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities capabilities = new NetworkCapabilities(); - if (!isMetered) { - capabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); - } + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, !isMetered); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); return new NetworkState(info, prop, capabilities, null, null, TEST_SSID); } @@ -1187,6 +1186,8 @@ public class NetworkStatsServiceTest { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities capabilities = new NetworkCapabilities(); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, !isRoaming); return new NetworkState(info, prop, capabilities, null, subscriberId, null); } @@ -1196,6 +1197,8 @@ public class NetworkStatsServiceTest { final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(iface); final NetworkCapabilities capabilities = new NetworkCapabilities(); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, false); + capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true); return new NetworkState(info, prop, capabilities, null, null, null); } |