Merge "Added interface to get the decimal digit from hex ICCID."
diff --git a/Android.bp b/Android.bp
index b28567a..7d75f62 100644
--- a/Android.bp
+++ b/Android.bp
@@ -68,9 +68,10 @@
subdirs = [
"cmds/*",
- "core/jni",
+ "core/*",
"libs/*",
"media/*",
+ "proto",
"tools/*",
"native/android",
"native/graphics/jni",
diff --git a/Android.mk b/Android.mk
index 2b12416..4e036b5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -906,8 +906,7 @@
# 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_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 \
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 @@
-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 @@
-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 518d972..3bff61f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18040,13 +18040,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
method public void close();
- method protected void finalize();
method public int getSpi();
}
@@ -39614,6 +39593,8 @@
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;
@@ -39625,8 +39606,13 @@
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;
@@ -39636,8 +39622,13 @@
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);
@@ -39648,8 +39639,13 @@
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 03fdf3a..d2f9197 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -19548,13 +19548,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
method public void close();
- method protected void finalize();
method public int getSpi();
}
@@ -43045,6 +43024,8 @@
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;
@@ -43056,8 +43037,13 @@
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;
@@ -43067,8 +43053,13 @@
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);
@@ -43079,8 +43070,13 @@
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 57c6581..d8a71a5 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -18092,13 +18092,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
method public void close();
- method protected void finalize();
method public int getSpi();
}
@@ -39838,6 +39817,8 @@
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;
@@ -39849,8 +39830,13 @@
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;
@@ -39860,8 +39846,13 @@
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);
@@ -39872,8 +39863,13 @@
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);
@@ -40025,6 +40021,7 @@
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
@@ -40046,6 +40043,7 @@
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 {
@@ -40723,6 +40721,7 @@
}
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);
@@ -40821,6 +40820,7 @@
}
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;
@@ -40828,6 +40828,37 @@
}
+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.bp b/core/java/Android.bp
new file mode 100644
index 0000000..42b0f6b
--- /dev/null
+++ b/core/java/Android.bp
@@ -0,0 +1,4 @@
+filegroup {
+ name: "IKeyAttestationApplicationIdProvider.aidl",
+ srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
+}
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java
index 5f61e07..d15dd6d 100644
--- a/core/java/android/app/DexLoadReporter.java
+++ b/core/java/android/app/DexLoadReporter.java
@@ -19,6 +19,7 @@
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 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 @@
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 @@
// The dex path is not a secondary dex file. Nothing to do.
return;
}
- File secondaryProfile = getSecondaryProfileFile(dexPath);
+
+ File realDexPath;
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.
+ // 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 {
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 @@
}
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 9c62f46..1f354af 100644
--- a/core/java/android/app/timezone/RulesUpdaterContract.java
+++ b/core/java/android/app/timezone/RulesUpdaterContract.java
@@ -83,8 +83,7 @@
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 d7e570f..f00e5c2 100644
--- a/core/java/android/app/usage/ExternalStorageStats.java
+++ b/core/java/android/app/usage/ExternalStorageStats.java
@@ -33,6 +33,7 @@
/** {@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 @@
}
/** {@hide} */
+ public @BytesLong long getObbBytes() {
+ return obbBytes;
+ }
+
+ /** {@hide} */
public ExternalStorageStats() {
}
@@ -107,6 +113,7 @@
this.videoBytes = in.readLong();
this.imageBytes = in.readLong();
this.appBytes = in.readLong();
+ this.obbBytes = in.readLong();
}
@Override
@@ -121,6 +128,7 @@
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 179f36d..e3d763a 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -31,7 +31,14 @@
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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
* 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 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 @@
} 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 @@
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 @@
}
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 @@
/**
* 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 2731935..d1efa2d 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
@@ -21,7 +21,16 @@
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 1f80ed7..ccc3ef4 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -19,7 +19,17 @@
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 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 @@
@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 @@
/** @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 d21d506..f01c493 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -19,7 +19,18 @@
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 @@
@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 3d407a6..5ccda0d 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -18,16 +18,24 @@
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 @@
/**
* 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/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4b44a17..c9afd6b 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -469,11 +469,19 @@
* 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 @@
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 3c868c3..903b602 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.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 4bb8844..db12dd9 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,6 +24,8 @@
import com.android.internal.util.BitUtils;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.StringJoiner;
@@ -77,6 +80,31 @@
*/
private long mNetworkCapabilities;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+ NET_CAPABILITY_MMS,
+ NET_CAPABILITY_SUPL,
+ NET_CAPABILITY_DUN,
+ NET_CAPABILITY_FOTA,
+ NET_CAPABILITY_IMS,
+ NET_CAPABILITY_CBS,
+ NET_CAPABILITY_WIFI_P2P,
+ NET_CAPABILITY_IA,
+ NET_CAPABILITY_RCS,
+ NET_CAPABILITY_XCAP,
+ NET_CAPABILITY_EIMS,
+ NET_CAPABILITY_NOT_METERED,
+ NET_CAPABILITY_INTERNET,
+ NET_CAPABILITY_NOT_RESTRICTED,
+ NET_CAPABILITY_TRUSTED,
+ NET_CAPABILITY_NOT_VPN,
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_CAPTIVE_PORTAL,
+ NET_CAPABILITY_FOREGROUND,
+ })
+ public @interface NetCapability { }
+
/**
* Indicates this is a network that has the ability to reach the
* carrier's MMSC for sending and receiving MMS messages.
@@ -260,11 +288,11 @@
* Multiple capabilities may be applied sequentially. Note that when searching
* for a network to satisfy a request, all capabilities requested must be satisfied.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
+ * @param capability the capability to be added.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities addCapability(int capability) {
+ public NetworkCapabilities addCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
throw new IllegalArgumentException("NetworkCapability out of range");
}
@@ -275,11 +303,11 @@
/**
* Removes (if found) the given capability from this {@code NetworkCapability} instance.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
+ * @param capability the capability to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities removeCapability(int capability) {
+ public NetworkCapabilities removeCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
throw new IllegalArgumentException("NetworkCapability out of range");
}
@@ -290,21 +318,20 @@
/**
* Gets all the capabilities set on this {@code NetworkCapability} instance.
*
- * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values
- * for this instance.
+ * @return an array of capability values for this instance.
* @hide
*/
- public int[] getCapabilities() {
+ public @NetCapability int[] getCapabilities() {
return BitUtils.unpackBits(mNetworkCapabilities);
}
/**
* Tests for the presence of a capabilitity on this instance.
*
- * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for.
+ * @param capability the capabilities to be tested for.
* @return {@code true} if set on this instance.
*/
- public boolean hasCapability(int capability) {
+ public boolean hasCapability(@NetCapability int capability) {
if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
return false;
}
@@ -385,6 +412,19 @@
*/
private long mTransportTypes;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "TRANSPORT_" }, value = {
+ TRANSPORT_CELLULAR,
+ TRANSPORT_WIFI,
+ TRANSPORT_BLUETOOTH,
+ TRANSPORT_ETHERNET,
+ TRANSPORT_VPN,
+ TRANSPORT_WIFI_AWARE,
+ TRANSPORT_LOWPAN,
+ })
+ public @interface Transport { }
+
/**
* Indicates this network uses a Cellular transport.
*/
@@ -426,7 +466,7 @@
public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
/** @hide */
- public static boolean isValidTransport(int transportType) {
+ public static boolean isValidTransport(@Transport int transportType) {
return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
}
@@ -449,11 +489,11 @@
* to be selected. This is logically different than
* {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
+ * @param transportType the transport type to be added.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities addTransportType(int transportType) {
+ public NetworkCapabilities addTransportType(@Transport int transportType) {
checkValidTransportType(transportType);
mTransportTypes |= 1 << transportType;
setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -463,11 +503,11 @@
/**
* Removes (if found) the given transport from this {@code NetworkCapability} instance.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
+ * @param transportType the transport type to be removed.
* @return This NetworkCapabilities instance, to facilitate chaining.
* @hide
*/
- public NetworkCapabilities removeTransportType(int transportType) {
+ public NetworkCapabilities removeTransportType(@Transport int transportType) {
checkValidTransportType(transportType);
mTransportTypes &= ~(1 << transportType);
setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -477,21 +517,20 @@
/**
* Gets all the transports set on this {@code NetworkCapability} instance.
*
- * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values
- * for this instance.
+ * @return an array of transport type values for this instance.
* @hide
*/
- public int[] getTransportTypes() {
+ public @Transport int[] getTransportTypes() {
return BitUtils.unpackBits(mTransportTypes);
}
/**
* Tests for the presence of a transport on this instance.
*
- * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
+ * @param transportType the transport type to be tested for.
* @return {@code true} if set on this instance.
*/
- public boolean hasTransport(int transportType) {
+ public boolean hasTransport(@Transport int transportType) {
return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
}
@@ -896,7 +935,7 @@
/**
* @hide
*/
- public static String capabilityNamesOf(int[] capabilities) {
+ public static String capabilityNamesOf(@NetCapability int[] capabilities) {
StringJoiner joiner = new StringJoiner("|");
if (capabilities != null) {
for (int c : capabilities) {
@@ -909,7 +948,7 @@
/**
* @hide
*/
- public static String capabilityNameOf(int capability) {
+ public static String capabilityNameOf(@NetCapability int capability) {
switch (capability) {
case NET_CAPABILITY_MMS: return "MMS";
case NET_CAPABILITY_SUPL: return "SUPL";
@@ -937,7 +976,7 @@
/**
* @hide
*/
- public static String transportNamesOf(int[] types) {
+ public static String transportNamesOf(@Transport int[] types) {
StringJoiner joiner = new StringJoiner("|");
if (types != null) {
for (int t : types) {
@@ -950,14 +989,14 @@
/**
* @hide
*/
- public static String transportNameOf(int transport) {
+ public static String transportNameOf(@Transport int transport) {
if (!isValidTransport(transport)) {
return "UNKNOWN";
}
return TRANSPORT_NAMES[transport];
}
- private static void checkValidTransportType(int transport) {
+ private static void checkValidTransportType(@Transport int transport) {
Preconditions.checkArgument(
isValidTransport(transport), "Invalid TransportType " + transport);
}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 95a8bb4..25b1705 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -155,14 +155,13 @@
* Add the given capability requirement to this builder. These represent
* the requested network's required capabilities. Note that when searching
* for a network to satisfy a request, all capabilities requested must be
- * satisfied. See {@link NetworkCapabilities} for {@code NET_CAPABILITY_*}
- * definitions.
+ * satisfied.
*
- * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add.
+ * @param capability The capability to add.
* @return The builder to facilitate chaining
* {@code builder.addCapability(...).addCapability();}.
*/
- public Builder addCapability(int capability) {
+ public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.addCapability(capability);
return this;
}
@@ -170,10 +169,10 @@
/**
* Removes (if found) the given capability from this builder instance.
*
- * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove.
+ * @param capability The capability to remove.
* @return The builder to facilitate chaining.
*/
- public Builder removeCapability(int capability) {
+ public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
mNetworkCapabilities.removeCapability(capability);
return this;
}
@@ -208,13 +207,12 @@
* Adds the given transport requirement to this builder. These represent
* the set of allowed transports for the request. Only networks using one
* of these transports will satisfy the request. If no particular transports
- * are required, none should be specified here. See {@link NetworkCapabilities}
- * for {@code TRANSPORT_*} definitions.
+ * are required, none should be specified here.
*
- * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add.
+ * @param transportType The transport type to add.
* @return The builder to facilitate chaining.
*/
- public Builder addTransportType(int transportType) {
+ public Builder addTransportType(@NetworkCapabilities.Transport int transportType) {
mNetworkCapabilities.addTransportType(transportType);
return this;
}
@@ -222,10 +220,10 @@
/**
* Removes (if found) the given transport from this builder instance.
*
- * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove.
+ * @param transportType The transport type to remove.
* @return The builder to facilitate chaining.
*/
- public Builder removeTransportType(int transportType) {
+ public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) {
mNetworkCapabilities.removeTransportType(transportType);
return this;
}
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index 0f1e259..d1ce60e 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 28cf42f..eb61c153 100644
--- a/core/java/android/net/metrics/DefaultNetworkEvent.java
+++ b/core/java/android/net/metrics/DefaultNetworkEvent.java
@@ -16,73 +16,43 @@
package android.net.metrics;
+import static android.net.ConnectivityManager.NETID_UNSET;
+
import android.net.NetworkCapabilities;
-import android.os.Parcel;
-import android.os.Parcelable;
/**
* An event recorded by ConnectivityService when there is a change in the default network.
* {@hide}
*/
-public final class DefaultNetworkEvent implements Parcelable {
+public class DefaultNetworkEvent {
+
// The ID of the network that has become the new default or NETID_UNSET if none.
- public final int netId;
+ 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 final int[] transportTypes;
+ public int[] transportTypes = new int[0];
// The ID of the network that was the default before or NETID_UNSET if none.
- public final int prevNetId;
+ public int prevNetId = NETID_UNSET;
// Whether the previous network had IPv4/IPv6 connectivity.
- public final boolean prevIPv4;
- public final boolean prevIPv6;
-
- public DefaultNetworkEvent(int netId, int[] transportTypes,
- int prevNetId, boolean prevIPv4, boolean prevIPv6) {
- this.netId = netId;
- this.transportTypes = transportTypes;
- this.prevNetId = prevNetId;
- this.prevIPv4 = prevIPv4;
- this.prevIPv6 = prevIPv6;
- }
-
- private DefaultNetworkEvent(Parcel in) {
- this.netId = in.readInt();
- this.transportTypes = in.createIntArray();
- this.prevNetId = in.readInt();
- this.prevIPv4 = (in.readByte() > 0);
- this.prevIPv6 = (in.readByte() > 0);
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(netId);
- out.writeIntArray(transportTypes);
- out.writeInt(prevNetId);
- out.writeByte(prevIPv4 ? (byte) 1 : (byte) 0);
- out.writeByte(prevIPv6 ? (byte) 1 : (byte) 0);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
+ public boolean prevIPv4;
+ public boolean prevIPv6;
@Override
public String toString() {
- String prevNetwork = String.valueOf(prevNetId);
- String newNetwork = String.valueOf(netId);
- if (prevNetId != 0) {
- prevNetwork += ":" + ipSupport();
- }
- if (netId != 0) {
- newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
- }
- return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+ String prevNetwork = String.valueOf(prevNetId);
+ String newNetwork = String.valueOf(netId);
+ if (prevNetId != 0) {
+ prevNetwork += ":" + ipSupport();
+ }
+ if (netId != 0) {
+ newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+ }
+ return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
}
private String ipSupport() {
if (prevIPv4 && prevIPv6) {
- return "DUAL";
+ return "IPv4v6";
}
if (prevIPv6) {
return "IPv6";
@@ -92,15 +62,4 @@
}
return "NONE";
}
-
- public static final Parcelable.Creator<DefaultNetworkEvent> CREATOR
- = new Parcelable.Creator<DefaultNetworkEvent>() {
- public DefaultNetworkEvent createFromParcel(Parcel in) {
- return new DefaultNetworkEvent(in);
- }
-
- public DefaultNetworkEvent[] newArray(int size) {
- return new DefaultNetworkEvent[size];
- }
- };
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index a83c7b3..3c1d83c 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.Slog;
@@ -25,12 +27,14 @@
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
@@ -88,6 +92,20 @@
*/
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.
/**
@@ -186,8 +204,11 @@
}
}
- /* 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;
@@ -217,7 +238,7 @@
* with their own uid. If the current thread is not currently executing an
* incoming transaction, then its own UserHandle is returned.
*/
- public static final UserHandle getCallingUserHandle() {
+ public static final @NonNull UserHandle getCallingUserHandle() {
return UserHandle.of(UserHandle.getUserId(getCallingUid()));
}
@@ -260,7 +281,7 @@
*
* @hide
*/
- public static final void withCleanCallingIdentity(ThrowingRunnable action) {
+ public static final void withCleanCallingIdentity(@NonNull ThrowingRunnable action) {
long callingIdentity = clearCallingIdentity();
Throwable throwableToPropagate = null;
try {
@@ -284,7 +305,7 @@
*
* @hide
*/
- public static final <T> T withCleanCallingIdentity(ThrowingSupplier<T> action) {
+ public static final <T> T withCleanCallingIdentity(@NonNull ThrowingSupplier<T> action) {
long callingIdentity = clearCallingIdentity();
Throwable throwableToPropagate = null;
try {
@@ -358,7 +379,8 @@
* 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();
@@ -376,7 +398,7 @@
* to return the given owner IInterface when the corresponding
* descriptor is requested.
*/
- public void attachInterface(IInterface owner, String descriptor) {
+ public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
@@ -384,7 +406,7 @@
/**
* Default implementation returns an empty interface name.
*/
- public String getInterfaceDescriptor() {
+ public @Nullable String getInterfaceDescriptor() {
return mDescriptor;
}
@@ -411,7 +433,7 @@
* associated IInterface if it matches the requested
* descriptor.
*/
- public IInterface queryLocalInterface(String descriptor) {
+ public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
if (mDescriptor.equals(descriptor)) {
return mOwner;
}
@@ -437,8 +459,20 @@
* to override this to do the appropriate unmarshalling of transactions.
*
* <p>If you want to call this, call transact().
+ *
+ * @param code The action to perform. This should
+ * be a number between {@link #FIRST_CALL_TRANSACTION} and
+ * {@link #LAST_CALL_TRANSACTION}.
+ * @param data Marshalled data being received from the caller.
+ * @param reply If the caller is expecting a result back, it should be marshalled
+ * in to here.
+ * @param flags Additional operation flags. Either 0 for a normal
+ * RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
+ *
+ * @return Return true on a successful call; returning false is generally used to
+ * indicate that you did not understand the transaction code.
*/
- protected boolean onTransact(int code, Parcel data, Parcel reply,
+ protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (code == INTERFACE_TRANSACTION) {
reply.writeString(getInterfaceDescriptor());
@@ -494,7 +528,7 @@
* Implemented to call the more convenient version
* {@link #dump(FileDescriptor, PrintWriter, String[])}.
*/
- public void dump(FileDescriptor fd, String[] args) {
+ public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) {
FileOutputStream fout = new FileOutputStream(fd);
PrintWriter pw = new FastPrintWriter(fout);
try {
@@ -530,7 +564,7 @@
* Like {@link #dump(FileDescriptor, String[])}, but ensures the target
* executes asynchronously.
*/
- public void dumpAsync(final FileDescriptor fd, final String[] args) {
+ public void dumpAsync(@NonNull final FileDescriptor fd, @Nullable final String[] args) {
final FileOutputStream fout = new FileOutputStream(fd);
final PrintWriter pw = new FastPrintWriter(fout);
Thread thr = new Thread("Binder.dumpAsync") {
@@ -553,7 +587,8 @@
* closed for you after you return.
* @param args additional arguments to the dump request.
*/
- protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
+ @Nullable String[] args) {
}
/**
@@ -566,9 +601,10 @@
* @throws RemoteException
* @hide
*/
- public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback,
- ResultReceiver resultReceiver) throws RemoteException {
+ public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
onShellCommand(in, out, err, args, callback, resultReceiver);
}
@@ -580,8 +616,10 @@
* Consider using {@link ShellCommand} to help in the implementation.</p>
* @hide
*/
- public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException {
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
FileOutputStream fout = new FileOutputStream(err != null ? err : out);
PrintWriter pw = new FastPrintWriter(fout);
pw.println("No shell command implementation.");
@@ -593,7 +631,7 @@
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
- public final boolean transact(int code, Parcel data, Parcel reply,
+ public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
@@ -610,24 +648,16 @@
/**
* Local implementation is a no-op.
*/
- public void linkToDeath(DeathRecipient recipient, int flags) {
+ public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
}
/**
* Local implementation is a no-op.
*/
- public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
+ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
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
@@ -651,8 +681,8 @@
}
}
- 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,
@@ -720,11 +750,207 @@
*/
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();
@@ -760,6 +986,7 @@
}
}
+ 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;
@@ -814,21 +1041,6 @@
}
}
- 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 {
@@ -840,19 +1052,9 @@
}
}
- // 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 75fea52..2efde23 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -23,7 +23,6 @@
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.TypedProperties;
-import dalvik.bytecode.OpcodeInfo;
import dalvik.system.VMDebug;
import org.apache.harmony.dalvik.ddmc.Chunk;
@@ -48,8 +47,6 @@
import java.util.Map;
-
-
/**
* Provides various debugging methods for Android applications, including
* tracing and allocation counts.
@@ -1948,13 +1945,7 @@
*/
@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];
}
/**
@@ -1964,13 +1955,7 @@
* @return true if counting was started
*/
public boolean resetAndStart() {
- try {
- VMDebug.startInstructionCounting();
- VMDebug.resetInstructionCount();
- } catch (UnsupportedOperationException uoe) {
- return false;
- }
- return true;
+ return false;
}
/**
@@ -1978,13 +1963,7 @@
* counting process.
*/
public boolean collect() {
- try {
- VMDebug.stopInstructionCounting();
- VMDebug.getInstructionCount(mCounts);
- } catch (UnsupportedOperationException uoe) {
- return false;
- }
- return true;
+ return false;
}
/**
@@ -1992,13 +1971,7 @@
* all threads).
*/
public int globalTotal() {
- int count = 0;
-
- for (int i = 0; i < NUM_INSTR; i++) {
- count += mCounts[i];
- }
-
- return count;
+ return 0;
}
/**
@@ -2006,15 +1979,7 @@
* 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/HwBinder.java b/core/java/android/os/HwBinder.java
index 270e63f..5e2a081 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 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 88226f0..5e9b9ae3 100644
--- a/core/java/android/os/HwBlob.java
+++ b/core/java/android/os/HwBlob.java
@@ -43,6 +43,18 @@
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 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/IBinder.java b/core/java/android/os/IBinder.java
index f762a05..20d8276 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -16,6 +16,9 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
import java.io.FileDescriptor;
/**
@@ -166,7 +169,7 @@
/**
* Get the canonical name of the interface supported by this binder.
*/
- public String getInterfaceDescriptor() throws RemoteException;
+ public @Nullable String getInterfaceDescriptor() throws RemoteException;
/**
* Check to see if the object still exists.
@@ -192,7 +195,7 @@
* to instantiate a proxy class to marshall calls through
* the transact() method.
*/
- public IInterface queryLocalInterface(String descriptor);
+ public @Nullable IInterface queryLocalInterface(@NonNull String descriptor);
/**
* Print the object's state into the given stream.
@@ -200,7 +203,7 @@
* @param fd The raw file descriptor that the dump is being sent to.
* @param args additional arguments to the dump request.
*/
- public void dump(FileDescriptor fd, String[] args) throws RemoteException;
+ public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException;
/**
* Like {@link #dump(FileDescriptor, String[])} but always executes
@@ -210,7 +213,8 @@
* @param fd The raw file descriptor that the dump is being sent to.
* @param args additional arguments to the dump request.
*/
- public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException;
+ public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args)
+ throws RemoteException;
/**
* Execute a shell command on this object. This may be performed asynchrously from the caller;
@@ -224,9 +228,10 @@
* @param resultReceiver Called when the command has finished executing, with the result code.
* @hide
*/
- public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
- String[] args, ShellCallback shellCallback,
- ResultReceiver resultReceiver) throws RemoteException;
+ public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback shellCallback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException;
/**
* Perform a generic operation with the object.
@@ -241,8 +246,12 @@
* null if you are not interested in the return value.
* @param flags Additional operation flags. Either 0 for a normal
* RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
+ *
+ * @return Returns the result from {@link Binder#onTransact}. A successful call
+ * generally returns true; false generally means the transaction code was not
+ * understood.
*/
- public boolean transact(int code, Parcel data, Parcel reply, int flags)
+ public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
throws RemoteException;
/**
@@ -271,7 +280,7 @@
*
* @see #unlinkToDeath
*/
- public void linkToDeath(DeathRecipient recipient, int flags)
+ public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
throws RemoteException;
/**
@@ -292,5 +301,5 @@
* exception will <em>not</em> be thrown, and you will receive a false
* return value instead.
*/
- public boolean unlinkToDeath(DeathRecipient recipient, int flags);
+ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);
}
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 81ea191..36e4c1c6 100644
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -27,9 +27,6 @@
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 7cb32ff..e2064a8 100644
--- a/core/java/com/android/internal/net/OWNERS
+++ b/core/java/com/android/internal/net/OWNERS
@@ -2,5 +2,5 @@
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 6d8b811..3cda820 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -546,13 +546,14 @@
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 @@
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/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index fe14d48..08d9527 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -35,6 +35,7 @@
#include <hidl/HidlTransportSupport.h>
#include <hwbinder/ProcessState.h>
#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
#include <vintf/parse_string.h>
#include <utils/misc.h>
@@ -261,14 +262,9 @@
JNIEnv *env,
jobject thiz,
jstring serviceNameObj) {
- if (serviceNameObj == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", NULL);
- return;
- }
-
- const char *serviceName = env->GetStringUTFChars(serviceNameObj, NULL);
- if (serviceName == NULL) {
- return; // XXX exception already pending?
+ ScopedUtfChars str(env, serviceNameObj);
+ if (str.c_str() == nullptr) {
+ return; // NPE will be pending.
}
sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz);
@@ -284,15 +280,12 @@
return;
}
- Return<bool> ret = manager->add(serviceName, base);
-
- env->ReleaseStringUTFChars(serviceNameObj, serviceName);
- serviceName = NULL;
+ Return<bool> ret = manager->add(str.c_str(), base);
bool ok = ret.isOk() && ret;
if (ok) {
- LOG(INFO) << "Starting thread pool.";
+ LOG(INFO) << "HwBinder: Starting thread pool for " << str.c_str();
::android::hardware::ProcessState::self()->startThreadPool();
}
@@ -303,36 +296,31 @@
JNIEnv *env,
jclass /* clazzObj */,
jstring ifaceNameObj,
- jstring serviceNameObj) {
+ jstring serviceNameObj,
+ jboolean retry) {
using ::android::hidl::base::V1_0::IBase;
using ::android::hardware::details::getRawServiceInternal;
- if (ifaceNameObj == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", NULL);
- return NULL;
- }
- if (serviceNameObj == NULL) {
- jniThrowException(env, "java/lang/NullPointerException", NULL);
- return NULL;
+ std::string ifaceName;
+ {
+ ScopedUtfChars str(env, ifaceNameObj);
+ if (str.c_str() == nullptr) {
+ return nullptr; // NPE will be pending.
+ }
+ ifaceName = str.c_str();
}
- const char *ifaceNameCStr = env->GetStringUTFChars(ifaceNameObj, NULL);
- if (ifaceNameCStr == NULL) {
- return NULL; // XXX exception already pending?
+ std::string serviceName;
+ {
+ ScopedUtfChars str(env, serviceNameObj);
+ if (str.c_str() == nullptr) {
+ return nullptr; // NPE will be pending.
+ }
+ serviceName = str.c_str();
}
- std::string ifaceName(ifaceNameCStr);
- env->ReleaseStringUTFChars(ifaceNameObj, ifaceNameCStr);
- const char *serviceNameCStr = env->GetStringUTFChars(serviceNameObj, NULL);
- if (serviceNameCStr == NULL) {
- return NULL; // XXX exception already pending?
- }
- std::string serviceName(serviceNameCStr);
- env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr);
-
- // 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) {
@@ -340,13 +328,14 @@
return NULL;
}
- LOG(INFO) << "Starting thread pool.";
+ LOG(INFO) << "HwBinder: Starting thread pool for " << serviceName << "::" << ifaceName;
::android::hardware::ProcessState::self()->startThreadPool();
return JHwRemoteBinder::NewObject(env, service);
}
-void JHwBinder_native_configureRpcThreadpool(jlong maxThreads, jboolean callerWillJoin) {
+void JHwBinder_native_configureRpcThreadpool(JNIEnv *, jclass,
+ jlong maxThreads, jboolean callerWillJoin) {
CHECK(maxThreads > 0);
ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);
}
@@ -355,7 +344,7 @@
IPCThreadState::self()->joinThreadPool();
}
-static void JHwBinder_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
+static void JHwBinder_report_sysprop_change(JNIEnv * /*env*/, jclass /*clazz*/)
{
report_sysprop_change();
}
@@ -371,7 +360,7 @@
{ "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 40d49b7..737ec47 100644
--- a/core/jni/android_os_HwBlob.cpp
+++ b/core/jni/android_os_HwBlob.cpp
@@ -60,12 +60,12 @@
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 +150,10 @@
return mBuffer;
}
+void *JHwBlob::data() {
+ return mBuffer;
+}
+
size_t JHwBlob::size() const {
return mSize;
}
@@ -242,8 +246,8 @@
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 +317,82 @@
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)); \
+}
+
+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_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)
+
#define DEFINE_BLOB_PUTTER(Suffix,Type) \
static void JHwBlob_native_put ## Suffix( \
JNIEnv *env, jobject thiz, jlong offset, Type x) { \
@@ -375,6 +455,72 @@
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) { \
+ \
+ if (array == nullptr) { \
+ jniThrowException(env, "java/lang/NullPointerException", nullptr); \
+ return; \
+ } \
+ \
+ sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz); \
+ \
+ jsize len = env->GetArrayLength(array); \
+ \
+ Type *src = \
+ env->Get ## NewType ## ArrayElements(array, nullptr /* isCopy */); \
+ \
+ status_t err = blob->write(offset, src, len * sizeof(Type)); \
+ \
+ env->Release ## NewType ## ArrayElements(array, src, 0 /* mode */); \
+ src = nullptr; \
+ \
+ 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) {
+
+ if (array == nullptr) {
+ jniThrowException(env, "java/lang/NullPointerException", nullptr);
+ return;
+ }
+
+ sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, thiz);
+
+ jsize len = env->GetArrayLength(array);
+
+ if ((offset + len * sizeof(bool)) > blob->size()) {
+ signalExceptionForError(env, -ERANGE);
+ return;
+ }
+
+ const jboolean *src =
+ env->GetBooleanArrayElements(array, nullptr /* isCopy */);
+
+ bool *dst = reinterpret_cast<bool *>(
+ static_cast<uint8_t *>(blob->data()) + offset);
+
+ for (jsize i = 0; i < len; ++i) {
+ dst[i] = src[i];
+ }
+
+ env->ReleaseBooleanArrayElements(
+ array, const_cast<jboolean *>(src), 0 /* mode */);
+
+ src = nullptr;
+}
+
static void JHwBlob_native_putBlob(
JNIEnv *env, jobject thiz, jlong offset, jobject blobObj) {
if (blobObj == nullptr) {
@@ -413,6 +559,14 @@
{ "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 +576,14 @@
{ "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 39393cb..6b1db63 100644
--- a/core/jni/android_os_HwBlob.h
+++ b/core/jni/android_os_HwBlob.h
@@ -50,6 +50,8 @@
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_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 768f53f..9f8d288 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -20,8 +20,7 @@
#include "android_os_Parcel.h"
#include "android_util_Binder.h"
-#include <nativehelper/JNIHelp.h>
-
+#include <atomic>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
@@ -44,8 +43,9 @@
#include <utils/SystemClock.h>
#include <utils/threads.h>
-#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
#include "core_jni_helpers.h"
@@ -97,14 +97,11 @@
{
// 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
@@ -133,24 +130,57 @@
jmethodID mCallback;
} gStrictModeCallbackOffsets;
-// ****************************************************************************
-// ****************************************************************************
-// ****************************************************************************
-
-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 void incRefsCreated(JNIEnv* env)
+static struct thread_dispatch_offsets_t
{
- int old = android_atomic_inc(&gNumRefsCreated);
- if (old == 200) {
- android_atomic_and(0, &gNumRefsCreated);
- env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
- gBinderInternalOffsets.mForceGc);
+ // Class state.
+ jclass mClass;
+ jmethodID mDispatchUncaughtException;
+ jmethodID mCurrentThread;
+} gThreadDispatchOffsets;
+
+// ****************************************************************************
+// ****************************************************************************
+// ****************************************************************************
+
+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;
+
+// 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.
+static void gcIfManyNewRefs(JNIEnv* env)
+{
+ 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);
}
}
@@ -166,69 +196,93 @@
return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL;
}
+// Report a java.lang.Error (or subclass). This will terminate the runtime by
+// calling FatalError with a message derived from the given error.
+static void report_java_lang_error_fatal_error(JNIEnv* env, jthrowable error,
+ const char* msg)
+{
+ // Report an error: reraise the exception and ask the runtime to abort.
+
+ // Try to get the exception string. Sometimes logcat isn't available,
+ // so try to add it to the abort message.
+ std::string exc_msg = "(Unknown exception message)";
+ {
+ ScopedLocalRef<jclass> exc_class(env, env->GetObjectClass(error));
+ jmethodID method_id = env->GetMethodID(exc_class.get(), "toString",
+ "()Ljava/lang/String;");
+ ScopedLocalRef<jstring> jstr(
+ env,
+ reinterpret_cast<jstring>(
+ env->CallObjectMethod(error, method_id)));
+ env->ExceptionClear(); // Just for good measure.
+ if (jstr.get() != nullptr) {
+ ScopedUtfChars jstr_utf(env, jstr.get());
+ if (jstr_utf.c_str() != nullptr) {
+ exc_msg = jstr_utf.c_str();
+ } else {
+ env->ExceptionClear();
+ }
+ }
+ }
+
+ env->Throw(error);
+ ALOGE("java.lang.Error thrown during binder transaction (stack trace follows) : ");
+ env->ExceptionDescribe();
+
+ std::string error_msg = base::StringPrintf(
+ "java.lang.Error thrown during binder transaction: %s",
+ exc_msg.c_str());
+ env->FatalError(error_msg.c_str());
+}
+
+// Report a java.lang.Error (or subclass). This will terminate the runtime, either by
+// the uncaught exception handler, or explicitly by calling
+// report_java_lang_error_fatal_error.
+static void report_java_lang_error(JNIEnv* env, jthrowable error, const char* msg)
+{
+ // Try to run the uncaught exception machinery.
+ jobject thread = env->CallStaticObjectMethod(gThreadDispatchOffsets.mClass,
+ gThreadDispatchOffsets.mCurrentThread);
+ if (thread != nullptr) {
+ env->CallVoidMethod(thread, gThreadDispatchOffsets.mDispatchUncaughtException,
+ error);
+ // Should not return here, unless more errors occured.
+ }
+ // Some error occurred that meant that either dispatchUncaughtException could not be
+ // called or that it had an error itself (as this should be unreachable under normal
+ // conditions). As the binder code cannot handle Errors, attempt to log the error and
+ // abort.
+ env->ExceptionClear();
+ report_java_lang_error_fatal_error(env, error, msg);
+}
+
static void report_exception(JNIEnv* env, jthrowable excep, const char* msg)
{
env->ExceptionClear();
- jstring tagstr = env->NewStringUTF(LOG_TAG);
- jstring msgstr = NULL;
- if (tagstr != NULL) {
- msgstr = env->NewStringUTF(msg);
+ ScopedLocalRef<jstring> tagstr(env, env->NewStringUTF(LOG_TAG));
+ ScopedLocalRef<jstring> msgstr(env);
+ if (tagstr != nullptr) {
+ msgstr.reset(env->NewStringUTF(msg));
}
- if ((tagstr == NULL) || (msgstr == NULL)) {
+ if ((tagstr != nullptr) && (msgstr != nullptr)) {
+ env->CallStaticIntMethod(gLogOffsets.mClass, gLogOffsets.mLogE,
+ tagstr.get(), msgstr.get(), excep);
+ if (env->ExceptionCheck()) {
+ // Attempting to log the failure has failed.
+ ALOGW("Failed trying to log exception, msg='%s'\n", msg);
+ env->ExceptionClear();
+ }
+ } else {
env->ExceptionClear(); /* assume exception (OOM?) was thrown */
ALOGE("Unable to call Log.e()\n");
ALOGE("%s", msg);
- goto bail;
- }
-
- env->CallStaticIntMethod(
- gLogOffsets.mClass, gLogOffsets.mLogE, tagstr, msgstr, excep);
- if (env->ExceptionCheck()) {
- /* attempting to log the failure has failed */
- ALOGW("Failed trying to log exception, msg='%s'\n", msg);
- env->ExceptionClear();
}
if (env->IsInstanceOf(excep, gErrorOffsets.mClass)) {
- /*
- * It's an Error: Reraise the exception and ask the runtime to abort.
- */
-
- // Try to get the exception string. Sometimes logcat isn't available,
- // so try to add it to the abort message.
- std::string exc_msg = "(Unknown exception message)";
- {
- ScopedLocalRef<jclass> exc_class(env, env->GetObjectClass(excep));
- jmethodID method_id = env->GetMethodID(exc_class.get(),
- "toString",
- "()Ljava/lang/String;");
- ScopedLocalRef<jstring> jstr(
- env,
- reinterpret_cast<jstring>(
- env->CallObjectMethod(excep, method_id)));
- env->ExceptionClear(); // Just for good measure.
- if (jstr.get() != nullptr) {
- ScopedUtfChars jstr_utf(env, jstr.get());
- exc_msg = jstr_utf.c_str();
- }
- }
-
- env->Throw(excep);
- ALOGE("java.lang.Error thrown during binder transaction (stack trace follows) : ");
- env->ExceptionDescribe();
-
- std::string error_msg = base::StringPrintf(
- "java.lang.Error thrown during binder transaction: %s",
- exc_msg.c_str());
- env->FatalError(error_msg.c_str());
+ report_java_lang_error(env, excep, msg);
}
-
-bail:
- /* discard local refs created for us by VM */
- env->DeleteLocalRef(tagstr);
- env->DeleteLocalRef(msgstr);
}
class JavaBBinderHolder;
@@ -236,12 +290,12 @@
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
@@ -258,7 +312,7 @@
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);
}
@@ -280,14 +334,11 @@
code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
if (env->ExceptionCheck()) {
- jthrowable excep = env->ExceptionOccurred();
- report_exception(env, excep,
+ ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
+ report_exception(env, excep.get(),
"*** Uncaught remote exception! "
"(Exceptions are not yet supported across processes.)");
res = JNI_FALSE;
-
- /* clean up JNI local ref -- we don't return to Java code */
- env->DeleteLocalRef(excep);
}
// Check if the strict mode state changed while processing the
@@ -299,11 +350,9 @@
}
if (env->ExceptionCheck()) {
- jthrowable excep = env->ExceptionOccurred();
- report_exception(env, excep,
+ ScopedLocalRef<jthrowable> excep(env, env->ExceptionOccurred());
+ report_exception(env, excep.get(),
"*** Uncaught exception in onBinderStrictModePolicyChange");
- /* clean up JNI local ref -- we don't return to Java code */
- env->DeleteLocalRef(excep);
}
// Need to always call through the native implementation of
@@ -325,12 +374,12 @@
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)
@@ -395,8 +444,8 @@
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)
@@ -446,9 +495,8 @@
if (mObject != NULL) {
result = env->IsSameObject(obj, mObject);
} else {
- jobject me = env->NewLocalRef(mObjectWeak);
- result = env->IsSameObject(obj, me);
- env->DeleteLocalRef(me);
+ ScopedLocalRef<jobject> me(env, env->NewLocalRef(mObjectWeak));
+ result = env->IsSameObject(obj, me.get());
}
return result;
}
@@ -477,7 +525,7 @@
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);
@@ -489,7 +537,7 @@
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;
};
@@ -554,21 +602,40 @@
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;
@@ -578,42 +645,31 @@
// 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;
@@ -623,15 +679,16 @@
{
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);
@@ -824,35 +881,21 @@
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)
@@ -871,8 +914,8 @@
{ "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 }
};
@@ -899,17 +942,18 @@
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;
}
}
@@ -944,8 +988,8 @@
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;
}
// ----------------------------------------------------------------------------
@@ -979,8 +1023,7 @@
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;
}
@@ -990,7 +1033,7 @@
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()),
@@ -1003,8 +1046,7 @@
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;
}
@@ -1126,8 +1168,7 @@
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;
@@ -1177,18 +1218,13 @@
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) {
@@ -1209,8 +1245,8 @@
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;
@@ -1222,8 +1258,7 @@
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) {
@@ -1249,25 +1284,21 @@
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;
}
// ----------------------------------------------------------------------------
@@ -1280,7 +1311,7 @@
{"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";
@@ -1292,14 +1323,11 @@
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;");
@@ -1337,5 +1365,12 @@
gStrictModeCallbackOffsets.mCallback = GetStaticMethodIDOrDie(env, clazz,
"onBinderStrictModePolicyChange", "(I)V");
+ clazz = FindClassOrDie(env, "java/lang/Thread");
+ gThreadDispatchOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
+ gThreadDispatchOffsets.mDispatchUncaughtException = GetMethodIDOrDie(env, clazz,
+ "dispatchUncaughtException", "(Ljava/lang/Throwable;)V");
+ gThreadDispatchOffsets.mCurrentThread = GetStaticMethodIDOrDie(env, clazz, "currentThread",
+ "()Ljava/lang/Thread;");
+
return 0;
}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
new file mode 100644
index 0000000..f7a3e1a
--- /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/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 9347877..3800e6f 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -213,27 +213,6 @@
}
@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 7bda231..3299cb2 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 @@
/**
* 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 e2ebbeb..9ac08f9 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 @@
@Mock
private A2dpProfile mA2dpProfile;
@Mock
- private HidProfile mHidProfile;
+ private PanProfile mPanProfile;
@Mock
private BluetoothDevice mDevice;
private CachedBluetoothDevice mCachedDevice;
@@ -65,7 +65,7 @@
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 @@
@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 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 @@
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 35a9477..b138df0 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/Android.bp b/proto/Android.bp
new file mode 100644
index 0000000..95f453c
--- /dev/null
+++ b/proto/Android.bp
@@ -0,0 +1,17 @@
+java_library_static {
+ name: "framework-protos",
+ host_supported: true,
+ proto: {
+ type: "nano",
+ },
+ srcs: ["src/**/*.proto"],
+ no_framework_libs: true,
+ target: {
+ android: {
+ jarjar_rules: "jarjar-rules.txt",
+ },
+ host: {
+ static_libs: ["libprotobuf-java-nano"],
+ },
+ },
+}
diff --git a/proto/Android.mk b/proto/Android.mk
deleted file mode 100644
index 1c03d16..0000000
--- a/proto/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := framework-protos
-
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_SRC_FILES:= $(call all-proto-files-under, src)
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Host-side version of framework-protos
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := host-framework-protos
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_SRC_FILES:= $(call all-proto-files-under, src)
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-nano
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt
-
-include $(BUILD_HOST_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5c11100..25c96d1 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -71,7 +71,6 @@
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.Uri;
-import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.util.MultinetworkPolicyTracker;
@@ -129,6 +128,7 @@
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
@@ -2283,7 +2283,7 @@
// 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.
- logDefaultNetworkEvent(null, nai);
+ metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai);
}
notifyIfacesChangedForNetworkStats();
// TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -5012,7 +5012,8 @@
// Notify system services that this network is up.
makeDefault(newNetwork);
// Log 0 -> X and Y -> X default network transitions, where X is the new default.
- logDefaultNetworkEvent(newNetwork, oldDefaultNetwork);
+ metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
+ newNetwork, oldDefaultNetwork);
// Have a new default network, release the transition wakelock in
scheduleReleaseNetworkTransitionWakelock();
}
@@ -5571,25 +5572,10 @@
return ServiceManager.checkService(name) != null;
}
- private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
- int newNetid = NETID_UNSET;
- int prevNetid = NETID_UNSET;
- int[] transports = new int[0];
- boolean hadIPv4 = false;
- boolean hadIPv6 = false;
-
- if (newNai != null) {
- newNetid = newNai.network.netId;
- transports = newNai.networkCapabilities.getTransportTypes();
- }
- if (prevNai != null) {
- prevNetid = prevNai.network.netId;
- final LinkProperties lp = prevNai.linkProperties;
- hadIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
- hadIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
- }
-
- mMetricsLog.log(new DefaultNetworkEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6));
+ @VisibleForTesting
+ protected IpConnectivityMetrics.Logger metricsLogger() {
+ return checkNotNull(LocalServices.getService(IpConnectivityMetrics.Logger.class),
+ "no IpConnectivityMetrics service");
}
private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 192fa3a..6339c9e 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -1447,7 +1447,9 @@
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 a139ac4..1154fbe 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -944,13 +944,13 @@
(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 f82eb1f..86ce12e 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 @@
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
new file mode 100644
index 0000000..8981db1
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.net.LinkProperties;
+import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.IpConnectivityLog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks events related to the default network for the purpose of default network metrics.
+ * {@hide}
+ */
+public class DefaultNetworkMetrics {
+
+ private static final int ROLLING_LOG_SIZE = 64;
+
+ // Event buffer used for metrics upload. The buffer is cleared when events are collected.
+ @GuardedBy("this")
+ private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();
+
+ public synchronized void listEvents(PrintWriter pw) {
+ long localTimeMs = System.currentTimeMillis();
+ for (DefaultNetworkEvent ev : mEvents) {
+ pw.println(ev);
+ }
+ }
+
+ public synchronized void listEventsAsProto(PrintWriter pw) {
+ for (DefaultNetworkEvent ev : mEvents) {
+ pw.print(IpConnectivityEventBuilder.toProto(ev));
+ }
+ }
+
+ public synchronized void flushEvents(List<IpConnectivityEvent> out) {
+ for (DefaultNetworkEvent ev : mEvents) {
+ out.add(IpConnectivityEventBuilder.toProto(ev));
+ }
+ 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();
+ }
+ 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();
+ }
+
+ mEvents.add(ev);
+ }
+}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 67e7216..3d71ecb 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -132,6 +132,18 @@
return out;
}
+ 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);
+ out.setDefaultNetworkEvent(ev);
+ return out;
+ }
+
private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) {
final IpConnectivityEvent ev = new IpConnectivityEvent();
ev.networkId = netId;
@@ -164,11 +176,6 @@
return true;
}
- if (in instanceof DefaultNetworkEvent) {
- setDefaultNetworkEvent(out, (DefaultNetworkEvent) in);
- return true;
- }
-
if (in instanceof NetworkEvent) {
setNetworkEvent(out, (NetworkEvent) in);
return true;
@@ -225,16 +232,6 @@
out.setIpReachabilityEvent(ipReachabilityEvent);
}
- private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) {
- IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent =
- new IpConnectivityLogClass.DefaultNetworkEvent();
- defaultNetworkEvent.networkId = netIdOf(in.netId);
- defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId);
- defaultNetworkEvent.transportTypes = in.transportTypes;
- defaultNetworkEvent.previousNetworkIpSupport = ipSupportOf(in);
- out.setDefaultNetworkEvent(defaultNetworkEvent);
- }
-
private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) {
IpConnectivityLogClass.NetworkEvent networkEvent =
new IpConnectivityLogClass.NetworkEvent();
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index f2445fa..24217e6 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -32,12 +32,15 @@
import android.util.ArrayMap;
import android.util.Base64;
import android.util.Log;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.RingBuffer;
import com.android.internal.util.TokenBucket;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -112,6 +115,9 @@
private final ToIntFunction<Context> mCapacityGetter;
+ @VisibleForTesting
+ final DefaultNetworkMetrics mDefaultNetworkMetrics = new DefaultNetworkMetrics();
+
public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
super(ctx);
mCapacityGetter = capacityGetter;
@@ -135,6 +141,8 @@
publishBinderService(SERVICE_NAME, impl);
publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
+
+ LocalServices.addService(Logger.class, new LoggerImpl());
}
}
@@ -188,6 +196,8 @@
final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
+ mDefaultNetworkMetrics.flushEvents(protoEvents);
+
if (mNetdListener != null) {
mNetdListener.flushStatistics(protoEvents);
}
@@ -228,6 +238,7 @@
if (mNetdListener != null) {
mNetdListener.listAsProtos(pw);
}
+ mDefaultNetworkMetrics.listEventsAsProto(pw);
return;
}
@@ -237,6 +248,7 @@
if (mNetdListener != null) {
mNetdListener.list(pw);
}
+ mDefaultNetworkMetrics.listEvents(pw);
}
/**
@@ -254,6 +266,7 @@
if (mNetdListener != null) {
mNetdListener.list(pw);
}
+ mDefaultNetworkMetrics.listEvents(pw);
}
private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -366,4 +379,15 @@
map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
return map;
}
+
+ /** Direct non-Binder interface for event producer clients within the system servers. */
+ public interface Logger {
+ DefaultNetworkMetrics defaultNetworkMetrics();
+ }
+
+ private class LoggerImpl implements Logger {
+ public DefaultNetworkMetrics defaultNetworkMetrics() {
+ return mDefaultNetworkMetrics;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 0993f42..d1bab89 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -28,6 +28,7 @@
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static com.android.server.ConnectivityService.SHORT_ARG;
import android.app.Notification;
@@ -60,6 +61,7 @@
import android.net.RouteInfo;
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
+import android.net.util.VersionedBroadcastListener;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
@@ -68,6 +70,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
@@ -180,6 +183,8 @@
// TODO: Figure out how to merge this and other downstream-tracking objects
// into a single coherent structure.
private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
+ private final VersionedBroadcastListener mCarrierConfigChange;
+ // TODO: Delete SimChangeListener; it's obsolete.
private final SimChangeListener mSimChange;
private volatile TetheringConfiguration mConfig;
@@ -220,11 +225,26 @@
mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
mForwardedDownstreams = new HashSet<>();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
+ mCarrierConfigChange = new VersionedBroadcastListener(
+ "CarrierConfigChangeListener", mContext, smHandler, filter,
+ (Intent ignored) -> {
+ mLog.log("OBSERVED carrier config change");
+ reevaluateSimCardProvisioning();
+ });
+ // TODO: Remove SimChangeListener altogether. For now, we retain it
+ // for logging purposes in case we need to debug something that might
+ // be related to changing signals from ACTION_SIM_STATE_CHANGED to
+ // ACTION_CARRIER_CONFIG_CHANGED.
mSimChange = new SimChangeListener(
- mContext, smHandler, () -> reevaluateSimCardProvisioning());
+ mContext, smHandler, () -> {
+ mLog.log("OBSERVED SIM card change");
+ });
mStateReceiver = new StateReceiver();
- IntentFilter filter = new IntentFilter();
+ filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
@@ -353,18 +373,30 @@
return false;
}
+ if (carrierConfigAffirmsEntitlementCheckNotRequired()) {
+ return false;
+ }
+ return (provisionApp.length == 2);
+ }
+
+ // The logic here is aimed solely at confirming that a CarrierConfig exists
+ // and affirms that entitlement checks are not required.
+ //
+ // TODO: find a better way to express this, or alter the checking process
+ // entirely so that this is more intuitive.
+ private boolean carrierConfigAffirmsEntitlementCheckNotRequired() {
// Check carrier config for entitlement checks
final CarrierConfigManager configManager = (CarrierConfigManager) mContext
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- if (configManager != null && configManager.getConfig() != null) {
- // we do have a CarrierConfigManager and it has a config.
- boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
- CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
- if (!isEntitlementCheckRequired) {
- return false;
- }
- }
- return (provisionApp.length == 2);
+ if (configManager == null) return false;
+
+ final PersistableBundle carrierConfig = configManager.getConfig();
+ if (carrierConfig == null) return false;
+
+ // A CarrierConfigManager was found and it has a config.
+ final boolean isEntitlementCheckRequired = carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+ return !isEntitlementCheckRequired;
}
// Used by the SIM card change observation code.
@@ -794,6 +826,7 @@
} else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
handleWifiApAction(intent);
} else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ mLog.log("OBSERVED configuration changed");
updateConfiguration();
}
}
@@ -1136,6 +1169,7 @@
private void reevaluateSimCardProvisioning() {
if (!hasMobileHotspotProvisionApp()) return;
+ if (carrierConfigAffirmsEntitlementCheckNotRequired()) return;
ArrayList<Integer> tethered = new ArrayList<>();
synchronized (mPublicSync) {
@@ -1503,6 +1537,7 @@
return;
}
+ mCarrierConfigChange.startListening();
mSimChange.startListening();
mUpstreamNetworkMonitor.start();
@@ -1520,6 +1555,7 @@
mOffload.stop();
mUpstreamNetworkMonitor.stop();
mSimChange.stopListening();
+ mCarrierConfigChange.stopListening();
notifyDownstreamsOfNewUpstreamIface(null);
handleNewUpstreamNetworkState(null);
}
diff --git a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
index 3e60f9f..33c9355 100644
--- a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
+++ b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
@@ -23,12 +23,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.net.util.VersionedBroadcastListener;
+import android.net.util.VersionedBroadcastListener.IntentCallback;
import android.os.Handler;
import android.util.Log;
import com.android.internal.telephony.TelephonyIntents;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
@@ -37,88 +40,40 @@
*
* @hide
*/
-public class SimChangeListener {
+public class SimChangeListener extends VersionedBroadcastListener {
private static final String TAG = SimChangeListener.class.getSimpleName();
private static final boolean DBG = false;
- private final Context mContext;
- private final Handler mTarget;
- private final AtomicInteger mSimBcastGenerationNumber;
- private final Runnable mCallback;
- private BroadcastReceiver mBroadcastReceiver;
-
public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) {
- mContext = ctx;
- mTarget = handler;
- mCallback = onSimCardLoadedCallback;
- mSimBcastGenerationNumber = new AtomicInteger(0);
+ super(TAG, ctx, handler, makeIntentFilter(), makeCallback(onSimCardLoadedCallback));
}
- public int generationNumber() {
- return mSimBcastGenerationNumber.get();
- }
-
- public void startListening() {
- if (DBG) Log.d(TAG, "startListening for SIM changes");
-
- if (mBroadcastReceiver != null) return;
-
- mBroadcastReceiver = new SimChangeBroadcastReceiver(
- mSimBcastGenerationNumber.incrementAndGet());
+ private static IntentFilter makeIntentFilter() {
final IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-
- mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget);
+ return filter;
}
- public void stopListening() {
- if (DBG) Log.d(TAG, "stopListening for SIM changes");
+ private static Consumer<Intent> makeCallback(Runnable onSimCardLoadedCallback) {
+ return new Consumer<Intent>() {
+ private boolean mSimNotLoadedSeen = false;
- if (mBroadcastReceiver == null) return;
+ @Override
+ public void accept(Intent intent) {
+ final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
+ Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
+ mSimNotLoadedSeen);
- mSimBcastGenerationNumber.incrementAndGet();
- mContext.unregisterReceiver(mBroadcastReceiver);
- mBroadcastReceiver = null;
- }
+ if (!INTENT_VALUE_ICC_LOADED.equals(state)) {
+ mSimNotLoadedSeen = true;
+ return;
+ }
- private boolean isSimCardLoaded(String state) {
- return INTENT_VALUE_ICC_LOADED.equals(state);
- }
-
- private class SimChangeBroadcastReceiver extends BroadcastReceiver {
- // used to verify this receiver is still current
- final private int mGenerationNumber;
-
- // used to check the sim state transition from non-loaded to loaded
- private boolean mSimNotLoadedSeen = false;
-
- public SimChangeBroadcastReceiver(int generationNumber) {
- mGenerationNumber = generationNumber;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final int currentGenerationNumber = mSimBcastGenerationNumber.get();
-
- if (DBG) {
- Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
- ", current generationNumber=" + currentGenerationNumber);
+ if (mSimNotLoadedSeen) {
+ mSimNotLoadedSeen = false;
+ onSimCardLoadedCallback.run();
+ }
}
- if (mGenerationNumber != currentGenerationNumber) return;
-
- final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
- Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
- mSimNotLoadedSeen);
-
- if (!isSimCardLoaded(state)) {
- mSimNotLoadedSeen = true;
- return;
- }
-
- if (mSimNotLoadedSeen) {
- mSimNotLoadedSeen = false;
- mCallback.run();
- }
- }
+ };
}
}
diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS
index 061fd8d..6b77d83 100644
--- a/services/core/java/com/android/server/net/OWNERS
+++ b/services/core/java/com/android/server/net/OWNERS
@@ -2,7 +2,7 @@
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 6749afb..e5c48cc 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 @@
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 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 @@
// 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 @@
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 @@
// 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 @@
}
// 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 @@
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 @@
if (failedPackageNames.contains(pkg)) {
// Skip previously failing package
continue;
- } else {
- // Conservatively add package to the list of failing ones in case performDexOpt
- // never returns.
- failedPackageNames.add(pkg);
}
}
+ 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 {
+ 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 @@
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 @@
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 5c4c040..210eb13 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 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 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 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 a43f8af..da6e26e 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 @@
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.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;
@@ -261,11 +259,12 @@
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 +279,7 @@
encodeParameter(builder, volumeUuid);
encodeParameter(builder, sharedLibraries);
encodeParameter(builder, seInfo);
+ encodeParameter(builder, downgrade);
commands.add(builder.toString());
}
@@ -314,12 +314,18 @@
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 7e9596a..dfa828d 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 @@
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,20 @@
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.List;
-import java.util.Set;
+import java.util.Map;
import dalvik.system.DexFile;
@@ -52,6 +53,7 @@
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 +125,16 @@
* 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,26 +147,19 @@
*/
@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);
-
- 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);
+ // 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.
+ String[] classLoaderContexts = DexoptUtils.getClassLoaderContexts(
+ pkg.applicationInfo, sharedLibraries);
int result = DEX_OPT_SKIPPED;
for (int i = 0; i < paths.size(); i++) {
@@ -176,16 +170,29 @@
}
// 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.isBootComplete());
+
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 +215,10 @@
*/
@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 +231,18 @@
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 +269,12 @@
* 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,9 +315,16 @@
}
@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)
@@ -323,19 +341,32 @@
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);
+ String classLoaderContext;
+ if (dexUseInfo.isUnknownClassLoaderContext() ||
+ dexUseInfo.isUnsupportedClassLoaderContext() ||
+ dexUseInfo.isVariableClassLoaderContext()) {
+ // If we have an unknown (not yet set), unsupported (custom class loaders), or a
+ // variable class loader chain, compile without a context and mark the oat file with
+ // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation.
+ // TODO(calin): We should just extract in this case.
+ classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
+ } else {
+ classLoaderContext = dexUseInfo.getClassLoaderContext();
+ }
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 +394,60 @@
/**
* 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 be 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();
+ for (String isa : dexUseInfo.getLoaderIsas()) {
+ String status = null;
+ try {
+ status = DexFile.getDexFileStatus(path, isa);
+ } catch (IOException ioe) {
+ status = "[Exception]: " + ioe.getMessage();
+ }
+ pw.println(isa + ": " + status);
+ }
+
+ pw.println("class loader context: " + dexUseInfo.getClassLoaderContext());
+ if (dexUseInfo.isUsedByOtherApps()) {
+ pw.println("used be other apps: " + dexUseInfo.getLoadingPackages());
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
}
}
@@ -436,11 +501,11 @@
* 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 +514,6 @@
}
/**
- * 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 +604,9 @@
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 da1df78..a8ea4ea 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.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,9 @@
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_LAST = REASON_AB_OTA;
+ public static final int REASON_LAST = REASON_INACTIVE_PACKAGE_DOWNGRADE;
/** 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 +9395,42 @@
// 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 +9461,8 @@
}
@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 +9470,7 @@
+ loadingPackageName + ", user=" + userId);
return;
}
- mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId);
+ mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId);
}
@Override
@@ -9476,17 +9498,53 @@
}
}
+ /**
+ * 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 performDexOpt(String packageName,
- boolean checkProfiles, int compileReason, boolean force, boolean bootComplete) {
+ 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 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 +9553,14 @@
* {@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 +9568,10 @@
// 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 +9582,7 @@
long callingId = Binder.clearCallingIdentity();
try {
synchronized (mInstallLock) {
- return performDexOptInternalWithDependenciesLI(p, checkProfiles,
- targetCompilerFilter, force, bootComplete);
+ return performDexOptInternalWithDependenciesLI(p, options);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -9566,12 +9602,11 @@
}
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 +9620,19 @@
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 +9758,7 @@
public void shutdown() {
mPackageUsage.writeNow(mPackages);
mCompilerStats.writeNow();
+ mDexManager.writePackageDexUsageNow();
}
@Override
@@ -9793,10 +9809,11 @@
// 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 +16244,7 @@
}
}
- 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");
@@ -18260,12 +18277,14 @@
// 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 */, false /* checkProfiles */,
- getCompilerFilterForReason(REASON_INSTALL),
+ null /* instructionSets */,
getOrCreateCompilerPackageStats(pkg),
- mDexManager.isUsedByOtherApps(pkg.packageName),
- true /* bootComplete */);
+ mDexManager.getPackageUseInfoOrDefault(pkg.packageName),
+ dexoptOptions);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -22536,7 +22555,8 @@
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 +25059,29 @@
}
}
}
+
+ 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 ec248f5..0a9480b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -26,7 +26,7 @@
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"
};
// Static block to ensure the strings array is of the right length.
@@ -47,7 +47,16 @@
// 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 {
+ sysPropValue = SystemProperties.get(sysPropName);
+ }
if (sysPropValue == null || sysPropValue.isEmpty() ||
!DexFile.isValidCompilerFilter(sysPropValue)) {
throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index eb1e7bd..1e0ce7a 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 @@
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 @@
}
/**
+ * 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 20d7b28..10ceba4 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 @@
String compilationReason = null;
String checkProfilesRaw = null;
boolean secondaryDex = false;
+ String split = null;
String opt;
while ((opt = getNextOption()) != null) {
@@ -395,6 +396,9 @@
case "--secondary-dex":
secondaryDex = true;
break;
+ case "--split":
+ split = getNextArgRequired();
+ break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
@@ -423,6 +427,16 @@
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 @@
targetCompilerFilter, forceCompilation)
: mInterface.performDexOptMode(packageName,
checkProfiles, targetCompilerFilter, forceCompilation,
- true /* bootComplete */);
+ true /* bootComplete */, split);
if (!result) {
failedPackages.add(packageName);
}
@@ -1609,7 +1623,7 @@
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 @@
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 be50eee..6274754 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 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 @@
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 @@
* 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
}
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 @@
* 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 @@
}
}
+ // 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 @@
// 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 @@
// 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 @@
}
/**
- * 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 @@
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 0000000..0966770
--- /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 0000000..bc8bf5e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -0,0 +1,327 @@
+/*
+ * 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 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.
+ *
+ * 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) {
+ // 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] = baseApkContextClassLoader;
+
+ 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] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader");
+ 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;
+ for (int i = 1; i < splitDependencies.size(); i++) {
+ getParentDependencies(splitDependencies.keyAt(i), 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");
+ classLoaderContexts[i] = encodeClassLoaderChain(
+ splitClassLoader, classLoaderContexts[i]);
+ }
+ }
+
+ 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}.
+ * 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.
+ */
+ private static String encodeClassLoader(String classpath, String classLoaderName) {
+ 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.
+ * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split
+ * dependencies {@see encodeClassLoader} separated by ';'.
+ */
+ private static String encodeClassLoaderChain(String cl1, String cl2) {
+ 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 8a66f12..a4a0a54 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.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.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 @@
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 @@
* 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 @@
// 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 @@
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 @@
}
// 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 @@
* 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 @@
*
* 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 @@
// Write the package line.
String packageName = pEntry.getKey();
PackageUseInfo packageUseInfo = pEntry.getValue();
+ fpw.println(packageName);
- fpw.println(String.join(SPLIT_CHAR, packageName,
- writeBoolean(packageUseInfo.mIsUsedByOtherApps)));
+ // 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 @@
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 @@
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 @@
// 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());
+
+ // 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: " + line);
}
- // We expect at least 3 elements (isUsedByOtherApps, userId, isa).
- String[] elems = s.split(SPLIT_CHAR);
- if (elems.length < 3) {
- throw new IllegalStateException("Invalid PackageDexUsage line: " + s);
- }
+ // 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 @@
"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 @@
}
/**
+ * 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 @@
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 @@
* 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 @@
* @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 @@
}
// 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 @@
* @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 @@
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 @@
/**
* 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 @@
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 @@
* 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 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 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 0000000..6c8c9b2
--- /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 bc0f6e4..6e6259d 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.Intent;
import android.content.IntentFilter;
import android.os.PatternMatcher;
+import android.os.UserHandle;
import android.util.Slog;
/**
@@ -76,7 +77,9 @@
// 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 2e0c21b..b89dd38 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.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 @@
@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 @@
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 6824a59..52b49ba 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.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 @@
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 @@
}
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/util/VersionedBroadcastListener.java b/services/net/java/android/net/util/VersionedBroadcastListener.java
new file mode 100644
index 0000000..107c404
--- /dev/null
+++ b/services/net/java/android/net/util/VersionedBroadcastListener.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * intents matching the provided filter arrive. Intents received by a stale
+ * receiver are safely ignored.
+ *
+ * Calls to startListening() and stopListening() must happen on the same thread.
+ *
+ * @hide
+ */
+public class VersionedBroadcastListener {
+ private static final boolean DBG = false;
+
+ public interface IntentCallback {
+ public void run(Intent intent);
+ }
+
+ private final String mTag;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final IntentFilter mFilter;
+ private final Consumer<Intent> mCallback;
+ private final AtomicInteger mGenerationNumber;
+ private BroadcastReceiver mReceiver;
+
+ public VersionedBroadcastListener(String tag, Context ctx, Handler handler,
+ IntentFilter filter, Consumer<Intent> callback) {
+ mTag = tag;
+ mContext = ctx;
+ mHandler = handler;
+ mFilter = filter;
+ mCallback = callback;
+ mGenerationNumber = new AtomicInteger(0);
+ }
+
+ public void startListening() {
+ if (DBG) Log.d(mTag, "startListening");
+ if (mReceiver != null) return;
+
+ mReceiver = new Receiver(mTag, mGenerationNumber, mCallback);
+ mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
+ }
+
+ public void stopListening() {
+ if (DBG) Log.d(mTag, "stopListening");
+ if (mReceiver == null) return;
+
+ mGenerationNumber.incrementAndGet();
+ mContext.unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
+
+ private static class Receiver extends BroadcastReceiver {
+ public final String tag;
+ public final AtomicInteger atomicGenerationNumber;
+ public final Consumer<Intent> callback;
+ // Used to verify this receiver is still current.
+ public final int generationNumber;
+
+ public Receiver(
+ String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+ this.tag = tag;
+ this.atomicGenerationNumber = atomicGenerationNumber;
+ this.callback = callback;
+ generationNumber = atomicGenerationNumber.incrementAndGet();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int currentGenerationNumber = atomicGenerationNumber.get();
+
+ if (DBG) {
+ Log.d(tag, "receiver generationNumber=" + generationNumber +
+ ", current generationNumber=" + currentGenerationNumber);
+ }
+ if (generationNumber != currentGenerationNumber) return;
+
+ callback.accept(intent);
+ }
+ }
+}
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 afc0f67..4db9a30 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.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 @@
@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 @@
private TestData mInvalidIsa;
private TestData mDoesNotExist;
+ private TestData mBarUser0UnsupportedClassLoader;
+ private TestData mBarUser0DelegateLastClassLoader;
+
private int mUser0;
private int mUser1;
@@ -68,12 +79,17 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
@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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
// The usedByOtherApps flag should be clear now.
pui = getPackageUseInfo(mBarUser0);
- assertNotNull(pui);
- assertFalse(pui.isUsedByOtherApps());
+ assertIsUsedByOtherApps(mBarUser0, pui, false);
}
@Test
@@ -275,8 +282,7 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 0000000..c2072df
--- /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 0000000..ff7bd72
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -0,0 +1,279 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+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 ApplicationInfo 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;
+
+ if (addSplits) {
+ 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"});
+
+ if (addSplitDependencies) {
+ ai.splitDependencies = new SparseArray<>(6 + 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
+ }
+ }
+ return ai;
+ }
+
+ @Test
+ public void testSplitChain() {
+ ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+ String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+ assertEquals(7, 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]);
+ }
+
+ @Test
+ public void testSplitChainNoSplitDependencies() {
+ ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false);
+ String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+ assertEquals(7, 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]);
+ }
+
+ @Test
+ public void testSplitChainNoIsolationNoSharedLibrary() {
+ ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+ ai.privateFlags = ai.privateFlags & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING);
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+ assertEquals(7, 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]);
+ }
+ @Test
+ public void testSplitChainNoSharedLibraries() {
+ ApplicationInfo ai = createMockApplicationInfo(
+ DELEGATE_LAST_CLASS_LOADER_NAME, true, true);
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+ assertEquals(7, 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]);
+ }
+
+ @Test
+ public void testSplitChainWithNullPrimaryClassLoader() {
+ // A null classLoaderName should mean PathClassLoader.
+ ApplicationInfo ai = createMockApplicationInfo(null, true, true);
+ String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+ assertEquals(7, 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]);
+ }
+
+ @Test
+ public void tesNoSplits() {
+ ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
+ String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+ assertEquals(1, contexts.length);
+ assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+ }
+
+ @Test
+ public void tesNoSplitsNullClassLoaderName() {
+ ApplicationInfo ai = createMockApplicationInfo(null, false, false);
+ String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+ assertEquals(1, contexts.length);
+ assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+ }
+
+ @Test
+ public void tesNoSplitDelegateLast() {
+ ApplicationInfo ai = createMockApplicationInfo(
+ DELEGATE_LAST_CLASS_LOADER_NAME, false, false);
+ String[] sharedLibrary = new String[] {"a.dex", "b.dex"};
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary);
+
+ assertEquals(1, contexts.length);
+ assertEquals("PCL[a.dex:b.dex]", contexts[0]);
+ }
+
+ @Test
+ public void tesNoSplitsNoSharedLibraries() {
+ ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false);
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+ assertEquals(1, contexts.length);
+ assertEquals("PCL[]", contexts[0]);
+ }
+
+ @Test
+ public void tesNoSplitDelegateLastNoSharedLibraries() {
+ ApplicationInfo ai = createMockApplicationInfo(
+ DELEGATE_LAST_CLASS_LOADER_NAME, false, false);
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+ assertEquals(1, contexts.length);
+ assertEquals("PCL[]", contexts[0]);
+ }
+
+ @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("DLC[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]);
+ assertEquals("DLC[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);
+ }
+}
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 2e99433..69a148d 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.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 @@
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 @@
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 @@
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 @@
mFooSplit2UsedByOtherApps0.mOwnerUserId,
mFooSplit2UsedByOtherApps0.mLoaderIsa,
/*mIsUsedByOtherApps*/false,
- mFooSplit2UsedByOtherApps0.mPrimaryOrSplit);
+ mFooSplit2UsedByOtherApps0.mPrimaryOrSplit,
+ mFooSplit2UsedByOtherApps0.mUsedBy);
assertPackageDexUsage(noLongerUsedByOtherApps);
}
@@ -332,14 +337,223 @@
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 @@
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 @@
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 9f4fb85..21b11b0 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 @@
res.videoBytes = stats[2];
res.imageBytes = stats[3];
res.appBytes = stats[4];
+ res.obbBytes = stats[5];
return res;
}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 6f970fa..a81fba9 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1449,6 +1449,12 @@
*/
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 @@
}
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 cd65232..2a707c9 100644
--- a/telecomm/java/android/telecom/DefaultDialerManager.java
+++ b/telecomm/java/android/telecom/DefaultDialerManager.java
@@ -170,7 +170,7 @@
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 @@
*
* @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 1d4a5cf..2e621f1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1508,6 +1508,13 @@
"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
*/
@@ -1593,6 +1600,13 @@
public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
"skip_cf_fail_to_disable_dialog_bool";
+ /**
+ * List of the FAC (feature access codes) to dial as a normal call.
+ * @hide
+ */
+ public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
+ "feature_access_codes_string_array";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1855,6 +1869,7 @@
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);
@@ -1864,6 +1879,7 @@
sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
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);
}
/**
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index b39b4c7..ddc938e 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -50,6 +51,10 @@
* 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 @@
mBasestationId = Integer.MAX_VALUE;
mLongitude = Integer.MAX_VALUE;
mLatitude = Integer.MAX_VALUE;
+ mAlphaLong = null;
+ mAlphaShort = null;
}
/**
@@ -75,19 +82,37 @@
* @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 @@
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 @@
}
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 @@
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 @@
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 ec008e2..6276626 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@
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 @@
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 @@
* @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 @@
* @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 @@
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 @@
@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 @@
}
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 @@
@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 @@
@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 @@
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 ce74383..74d2966 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@
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 @@
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 @@
* @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 @@
* @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 @@
/**
* @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 @@
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 @@
}
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 @@
@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 @@
@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 @@
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 0d13efd..51b11aa 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -19,6 +19,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import android.text.TextUtils;
import java.util.Objects;
@@ -30,10 +31,6 @@
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 @@
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 @@
* @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 @@
* @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 @@
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 @@
}
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 @@
@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 @@
@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 @@
private static void log(String s) {
Rlog.w(LOG_TAG, s);
}
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 98fb653..c3a2ceb 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -273,6 +273,13 @@
* {@hide}
*/
public static final int EMERGENCY_PERM_FAILURE = 64;
+
+ /**
+ * This cause is used to report a normal event only when no other cause in the normal class
+ * applies.
+ * {@hide}
+ */
+ public static final int NORMAL_UNSPECIFIED = 65;
//*********************************************************************************************
// When adding a disconnect type:
// 1) Update toString() with the newly added disconnect type.
@@ -413,6 +420,8 @@
return "EMERGENCY_TEMP_FAILURE";
case EMERGENCY_PERM_FAILURE:
return "EMERGENCY_PERM_FAILURE";
+ case NORMAL_UNSPECIFIED:
+ return "NORMAL_UNSPECIFIED";
default:
return "INVALID: " + cause;
}
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index 9a9877a..f392570 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -21,6 +21,7 @@
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 @@
"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 a8c4607..fb2ff7b 100644
--- a/telephony/java/android/telephony/MbmsStreamingSession.java
+++ b/telephony/java/android/telephony/MbmsStreamingSession.java
@@ -20,6 +20,7 @@
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 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 c8b4776..de02de7 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -68,6 +68,7 @@
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 @@
mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = true;
+ mUseOnlyRsrpForLteLevel = false;
}
/**
@@ -134,6 +136,7 @@
mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = gsmFlag;
+ mUseOnlyRsrpForLteLevel = false;
}
/**
@@ -145,10 +148,10 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* @param lteCqi
* @param lteRsrpBoost
* @param gsm
+ * @param useOnlyRsrpForLteLevel
*
* @hide
*/
@@ -255,7 +259,7 @@
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 @@
mLteRsrpBoost = lteRsrpBoost;
mTdScdmaRscp = INVALID;
isGsm = gsm;
+ mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
if (DBG) log("initialize: " + toString());
}
@@ -293,6 +298,7 @@
mLteRsrpBoost = s.mLteRsrpBoost;
mTdScdmaRscp = s.mTdScdmaRscp;
isGsm = s.isGsm;
+ mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
}
/**
@@ -318,6 +324,7 @@
mLteRsrpBoost = in.readInt();
mTdScdmaRscp = in.readInt();
isGsm = (in.readInt() != 0);
+ mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
}
/**
@@ -366,6 +373,7 @@
out.writeInt(mLteRsrpBoost);
out.writeInt(mTdScdmaRscp);
out.writeInt(isGsm ? 1 : 0);
+ out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
}
/**
@@ -449,6 +457,17 @@
}
/**
+ * @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 @@
}
}
+ 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 @@
}
/**
+ * @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 @@
+ (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 @@
&& mLteCqi == s.mLteCqi
&& mLteRsrpBoost == s.mLteRsrpBoost
&& mTdScdmaRscp == s.mTdScdmaRscp
- && isGsm == s.isGsm);
+ && isGsm == s.isGsm
+ && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel);
}
/**
@@ -1031,7 +1068,9 @@
+ " " + 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 @@
mLteRsrpBoost = m.getInt("lteRsrpBoost");
mTdScdmaRscp = m.getInt("TdScdma");
isGsm = m.getBoolean("isGsm");
+ mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
}
/**
@@ -1110,6 +1150,7 @@
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 6029995..31ee315 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -387,6 +387,112 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
* <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 @@
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 dcdda86..ec84050 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -267,6 +267,31 @@
}
/**
+ * 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 @@
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/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index d8d7f48..b30a3af 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 @@
/** @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 d38d8a7..b4ad1d7 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -22,6 +22,8 @@
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 @@
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 c704f34..ef2a14a 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 @@
* @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 9ccdd56..4fee3df 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -18,6 +18,7 @@
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 @@
* @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 a238153..db177c0 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -18,6 +18,7 @@
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 @@
* @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 c57ef98..0afde88 100644
--- a/telephony/java/com/android/ims/ImsConferenceState.java
+++ b/telephony/java/com/android/ims/ImsConferenceState.java
@@ -79,6 +79,8 @@
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 @@
} 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 fe37531..a4eb424 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -187,6 +187,57 @@
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 @@
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 7a53ef6..14c5f4b 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 @@
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 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 @@
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 @@
}
/**
+ * 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 @@
*/
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 @@
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 1ca19e0..4f5bfa9 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 @@
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 @@
}
/**
+ * 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 @@
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 @@
}
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 @@
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 @@
}
/**
+ * 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/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
new file mode 100644
index 0000000..39f59f1
--- /dev/null
+++ b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.reset;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VersionedBroadcastListenerTest {
+ private static final String TAG = VersionedBroadcastListenerTest.class.getSimpleName();
+ private static final String ACTION_TEST = "action.test.happy.broadcasts";
+
+ @Mock private Context mContext;
+ private BroadcastInterceptingContext mServiceContext;
+ private Handler mHandler;
+ private VersionedBroadcastListener mListener;
+ private int mCallbackCount;
+
+ private void doCallback() { mCallbackCount++; }
+
+ private class MockContext extends BroadcastInterceptingContext {
+ MockContext(Context base) {
+ super(base);
+ }
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ reset(mContext);
+ mServiceContext = new MockContext(mContext);
+ mHandler = new Handler(Looper.myLooper());
+ mCallbackCount = 0;
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_TEST);
+ mListener = new VersionedBroadcastListener(
+ TAG, mServiceContext, mHandler, filter, (Intent intent) -> doCallback());
+ }
+
+ @After public void tearDown() throws Exception {
+ if (mListener != null) {
+ mListener.stopListening();
+ mListener = null;
+ }
+ }
+
+ private void sendBroadcast() {
+ final Intent intent = new Intent(ACTION_TEST);
+ mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ @Test
+ public void testBasicListening() {
+ assertEquals(0, mCallbackCount);
+ mListener.startListening();
+ for (int i = 0; i < 5; i++) {
+ sendBroadcast();
+ assertEquals(i+1, mCallbackCount);
+ }
+ mListener.stopListening();
+ }
+
+ @Test
+ public void testBroadcastsBeforeStartAreIgnored() {
+ assertEquals(0, mCallbackCount);
+ for (int i = 0; i < 5; i++) {
+ sendBroadcast();
+ assertEquals(0, mCallbackCount);
+ }
+
+ mListener.startListening();
+ sendBroadcast();
+ assertEquals(1, mCallbackCount);
+ }
+
+ @Test
+ public void testBroadcastsAfterStopAreIgnored() {
+ mListener.startListening();
+ sendBroadcast();
+ assertEquals(1, mCallbackCount);
+ mListener.stopListening();
+
+ for (int i = 0; i < 5; i++) {
+ sendBroadcast();
+ assertEquals(1, mCallbackCount);
+ }
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index c2cb66d..27a29b6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -104,6 +104,8 @@
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.DefaultNetworkMetrics;
+import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
@@ -156,6 +158,9 @@
private MockNetworkAgent mEthernetNetworkAgent;
private Context mContext;
+ @Mock IpConnectivityMetrics.Logger mMetricsService;
+ @Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
+
// 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
// reflect the state of our test ConnectivityService.
@@ -805,6 +810,11 @@
return Context.ETHERNET_SERVICE.equals(name);
}
+ @Override
+ protected IpConnectivityMetrics.Logger metricsLogger() {
+ return mMetricsService;
+ }
+
public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
return mLastCreatedNetworkMonitor;
}
@@ -833,6 +843,9 @@
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getContext();
+ MockitoAnnotations.initMocks(this);
+ when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
+
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 .
if (Looper.myLooper() == null) {
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index b4b8094..9e97d84b 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -206,7 +206,7 @@
eq(CRYPT_KEY),
anyInt(),
eq(""),
- isNull(),
+ eq(new byte[] {}),
eq(0),
anyInt(),
anyInt(),
@@ -227,7 +227,7 @@
eq(CRYPT_KEY),
anyInt(),
eq(""),
- isNull(),
+ eq(new byte[] {}),
eq(0),
anyInt(),
anyInt(),
@@ -256,10 +256,10 @@
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 @@
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 2624176..ad6ebf9 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -198,21 +198,20 @@
@Test
public void testDefaultNetworkEventSerialization() {
- ConnectivityMetricsEvent ev = describeIpEvent(
- aType(DefaultNetworkEvent.class),
- anInt(102),
- anIntArray(1, 2, 3),
- anInt(101),
- aBool(true),
- aBool(false));
+ DefaultNetworkEvent ev = new DefaultNetworkEvent();
+ ev.netId = 102;
+ ev.prevNetId = 101;
+ ev.transportTypes = new int[]{1, 2, 3};
+ ev.prevIPv4 = true;
+ ev.prevIPv6 = true;
String want = String.join("\n",
"dropped_events: 0",
"events <",
" if_name: \"\"",
" link_layer: 0",
- " network_id: 0",
- " time_ms: 1",
+ " network_id: 102",
+ " time_ms: 0",
" transports: 0",
" default_network_event <",
" default_network_duration_ms: 0",
@@ -226,7 +225,7 @@
" previous_network_id <",
" network_id: 101",
" >",
- " previous_network_ip_support: 1",
+ " previous_network_ip_support: 3",
" transport_types: 1",
" transport_types: 2",
" transport_types: 3",
@@ -234,7 +233,7 @@
">",
"version: 2\n");
- verifySerialization(want, ev);
+ verifySerialization(want, IpConnectivityEventBuilder.toProto(ev));
}
@Test
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index a395c48..6c1decc 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -18,6 +18,7 @@
import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,6 +31,10 @@
import android.net.ConnectivityManager;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.metrics.ApfProgramEvent;
@@ -41,18 +46,22 @@
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
-import android.system.OsConstants;
import android.os.Parcelable;
import android.support.test.runner.AndroidJUnit4;
+import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
+
+import com.android.internal.util.BitUtils;
import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
+
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -162,6 +171,144 @@
}
@Test
+ public void testDefaultNetworkEvents() throws Exception {
+ final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+ final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
+
+ NetworkAgentInfo[][] defaultNetworks = {
+ // nothing -> cell
+ {null, makeNai(100, 10, false, true, cell)},
+ // cell -> wifi
+ {makeNai(100, 50, true, true, cell), makeNai(101, 20, true, false, wifi)},
+ // wifi -> nothing
+ {makeNai(101, 60, true, false, wifi), null},
+ // nothing -> cell
+ {null, makeNai(102, 10, true, true, cell)},
+ // cell -> wifi
+ {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)},
+ };
+
+ for (NetworkAgentInfo[] pair : defaultNetworks) {
+ mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(pair[1], pair[0]);
+ }
+
+ String want = String.join("\n",
+ "dropped_events: 0",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 100",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " 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_network_ip_support: 0",
+ " transport_types: 0",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 101",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 101",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 100",
+ " >",
+ " previous_network_ip_support: 3",
+ " transport_types: 1",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 0",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 0",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 101",
+ " >",
+ " previous_network_ip_support: 1",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 102",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " 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_network_ip_support: 0",
+ " transport_types: 0",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 103",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 103",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 102",
+ " >",
+ " previous_network_ip_support: 3",
+ " transport_types: 1",
+ " >",
+ ">",
+ "version: 2\n");
+
+ verifySerialization(want, getdump("flush"));
+ }
+
+ @Test
public void testEndToEndLogging() throws Exception {
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
@@ -194,7 +341,6 @@
Parcelable[] events = {
new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
new DhcpClientEvent("SomeState", 192),
- new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
validationEv,
apfStats,
@@ -233,6 +379,13 @@
wakeupEvent("wlan0", 10008);
wakeupEvent("rmnet0", 1000);
+ 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);
+
String want = String.join("\n",
"dropped_events: 0",
"events <",
@@ -264,30 +417,6 @@
" network_id: 0",
" time_ms: 300",
" transports: 0",
- " default_network_event <",
- " default_network_duration_ms: 0",
- " 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: 101",
- " >",
- " previous_network_ip_support: 1",
- " transport_types: 1",
- " transport_types: 2",
- " transport_types: 3",
- " >",
- ">",
- "events <",
- " if_name: \"\"",
- " link_layer: 4",
- " network_id: 0",
- " time_ms: 400",
- " transports: 0",
" ip_provisioning_event <",
" event_type: 1",
" if_name: \"\"",
@@ -298,7 +427,7 @@
" if_name: \"\"",
" link_layer: 4",
" network_id: 0",
- " time_ms: 500",
+ " time_ms: 400",
" transports: 0",
" validation_probe_event <",
" latency_ms: 40730",
@@ -310,7 +439,7 @@
" if_name: \"\"",
" link_layer: 4",
" network_id: 0",
- " time_ms: 600",
+ " time_ms: 500",
" transports: 0",
" apf_statistics <",
" dropped_ras: 2",
@@ -331,7 +460,7 @@
" if_name: \"\"",
" link_layer: 4",
" network_id: 0",
- " time_ms: 700",
+ " time_ms: 600",
" transports: 0",
" ra_event <",
" dnssl_lifetime: -1",
@@ -344,6 +473,50 @@
">",
"events <",
" if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 100",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " 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_network_ip_support: 0",
+ " transport_types: 0",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
+ " link_layer: 0",
+ " network_id: 101",
+ " time_ms: 0",
+ " transports: 0",
+ " default_network_event <",
+ " default_network_duration_ms: 0",
+ " final_score: 0",
+ " initial_score: 0",
+ " ip_support: 0",
+ " network_id <",
+ " network_id: 101",
+ " >",
+ " no_default_network_duration_ms: 0",
+ " previous_network_id <",
+ " network_id: 100",
+ " >",
+ " previous_network_ip_support: 2",
+ " transport_types: 1",
+ " >",
+ ">",
+ "events <",
+ " if_name: \"\"",
" link_layer: 4",
" network_id: 100",
" time_ms: 0",
@@ -471,6 +644,26 @@
mNetdListener.onWakeupEvent(prefix, uid, uid, 0);
}
+ NetworkAgentInfo makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports) {
+ NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
+ when(nai.network()).thenReturn(new Network(netId));
+ when(nai.getCurrentScore()).thenReturn(score);
+ nai.linkProperties = new LinkProperties();
+ nai.networkCapabilities = new NetworkCapabilities();
+ for (int t : BitUtils.unpackBits(transports)) {
+ nai.networkCapabilities.addTransportType(t);
+ }
+ if (ipv4) {
+ nai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.12/24"));
+ nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
+ }
+ if (ipv6) {
+ nai.linkProperties.addLinkAddress(new LinkAddress("2001:db8:dead:beef:f00::a0/64"));
+ nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("::/0")));
+ }
+ return nai;
+ }
+
List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
ArgumentCaptor<ConnectivityMetricsEvent> captor =
ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
index b5d333b..f58ea7e 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
@@ -48,8 +48,6 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SimChangeListenerTest {
- private static final int EVENT_UNM_UPDATE = 1;
-
@Mock private Context mContext;
private BroadcastInterceptingContext mServiceContext;
private Handler mHandler;
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
new file mode 100644
index 0000000..6dd6059
--- /dev/null
+++ b/tools/locked_region_code_injection/Android.bp
@@ -0,0 +1,12 @@
+java_library_host {
+ name: "lockedregioncodeinjection",
+ manifest: "manifest.txt",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "asm-6.0",
+ "asm-commons-6.0",
+ "asm-tree-6.0",
+ "asm-analysis-6.0",
+ "guava-21.0",
+ ],
+}
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
deleted file mode 100644
index 3f65151..0000000
--- a/tools/locked_region_code_injection/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_JAR_MANIFEST := manifest.txt
-LOCAL_MODULE := lockedregioncodeinjection
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := \
- asm-6.0 \
- asm-commons-6.0 \
- asm-tree-6.0 \
- asm-analysis-6.0 \
- guava-21.0 \
-
-include $(BUILD_HOST_JAVA_LIBRARY)